Ember
Loading...
Searching...
No Matches
MainFrame.cpp
Go to the documentation of this file.
1#include "App/MainFrame.h"
2#include "Adapters/DirectTreeAdapter.h" // NEW: Include DirectTreeAdapter
3#include "Adapters/NodeAdapter.h" // NEW: Include adapter for abstraction compatibility
4#include "Components/TabFactory.h" // Tab factory for creating tabs
5#include "Core/BehaviorTree.h" // BehaviorTree class for tree storage
6#include "Core/BehaviorTreeValidator.h" // NEW: Include unified validator
7#include "Dialogs/BehaviorTreeProjectDialog.h" // Project creation/editing dialog
8#include "Dialogs/LoadingDialog.h" // NEW: Include LoadingDialog for detailed loading window
9#include "Dialogs/OverrideSceneDialog.h" // NEW: Include custom override scene dialog
10#include "Dialogs/ParserConfigDialog.h" // Parser configuration dialog
11#include "Dialogs/PreferencesDialog.h" // NEW: Include preferences dialog
12#include "Dialogs/ProjectManagerDialog.h" // Project opening dialog
13#include "Panels/BottomPanel.h"
15#include "Panels/MainPanel.h"
16#include "Panels/RightSidePanel.h"
17#include "Parsers/BehaviorTreeProject.h" // BehaviorTree project class
18#include "Parsers/ConfigManager.h" // NEW: Include ConfigManager for parser profiles
19#include "Parsers/LibXMLBehaviorTreeParser.h" // Include libxml2 parser
20#include "Parsers/LibXMLBehaviorTreeSerializer.h" // NEW: Include libxml2 serializer
21#include "Parsers/ProjectManager.h" // Project manager singleton
26#include "Tabs/PropertiesTab.h"
27#include "Utils/AppPreferences.h" // NEW: Include application preferences system
28#include "Utils/BehaviorTreeConfig.h" // NEW: Include configuration system
29#include "Utils/CustomAuiNotebook.h" // CustomAuiNotebook for panel notebooks
30#include "Utils/DPI.h"
31#include "Utils/Logger.h" // NEW: Include unified logging system
32#include "Utils/PerformanceMonitor.h" // NEW: Include performance monitoring
33#include "Utils/ResourcePath.h" // Centralized resource path management
34#include <chrono>
35#include <ctime>
36#include <fstream>
37#include <wx/artprov.h> // Include for wxArtProvider
38#include <wx/aui/aui.h>
39#include <wx/filename.h> // NEW: Include wxFileName for file operations
40#include <wx/splitter.h>
41#include <wx/stdpaths.h> // NEW: Include wxStandardPaths for getting executable directory
42#include <wx/tglbtn.h> // Include for wxToggleButton
43
45 EVT_MENU(ID_LoadXML, MainFrame::OnLoadXML) // Event for XML loading (libxml2)
46 EVT_MENU(ID_SaveXML, MainFrame::OnSaveXML) // Event for XML saving
47 EVT_MENU(ID_SaveAsXML, MainFrame::OnSaveAsXML) // Event for XML save as
48 EVT_MENU(ID_ResetZoom, MainFrame::OnResetZoom) // Event for reset zoom hotkey
49
50 // Project menu events
53 EVT_MENU(ID_ProjectSettings, MainFrame::OnProjectSettings)
54
55 // View menu events
58
59 // Settings menu events
61
62 // Updated tool button events for new 3-button layout
64 EVT_BUTTON(ID_LogReplayTool, MainFrame::OnLogReplayTool)
65
66 // Panel toggle events
67 EVT_BUTTON(ID_ToggleLeftPanel, MainFrame::OnToggleLeftPanel)
68 EVT_BUTTON(ID_ToggleBottomPanel, MainFrame::OnToggleBottomPanel)
69 EVT_BUTTON(ID_ToggleRightPanel, MainFrame::OnToggleRightPanel)
70
71 // Performance monitoring events
72 EVT_PAINT(MainFrame::OnPaint) EVT_IDLE(MainFrame::OnIdle) EVT_SIZE(MainFrame::OnFrameResize) wxEND_EVENT_TABLE()
73
74 MainFrame::MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size, long style)
75 : wxFrame(NULL, wxID_ANY, title, pos, size, style), m_auiManager(nullptr), m_bottomPanel(nullptr),
76 m_scenePanel(nullptr), m_panelToggleToolbar(nullptr) {
77 // Initialize unified AUI manager
78 m_auiManager = new wxAuiManager(this);
79
80 // Increase sash size for easier resizing
81 m_auiManager->GetArtProvider()->SetMetric(wxAUI_DOCKART_SASH_SIZE, 8);
82
83 LOG_INFO("AUI", "Initialized unified AUI manager for MainFrame");
84
85 // Load application preferences
88 if (prefs.LoadFromFile(configPath)) {
89 LOG_INFO("Preferences", "Application preferences loaded from: " + configPath);
90 } else {
91 LOG_INFO("Preferences", "Using default preferences (config file not found or invalid)");
92 }
93
94 CreateMenuAndStatusBar();
95 CreatePanelToggleToolbar();
96 InitializeConfigurationMenu();
97 CreatePanelLayout();
98
99 // Apply window startup mode preferences
100 const auto &windowSettings = prefs.GetWindowSettings();
101
102 // Apply status bar visibility preference
103 if (windowSettings.showStatusBar) {
104 GetStatusBar()->Show();
105 LOG_INFO("Preferences", "Status bar enabled");
106 } else {
107 GetStatusBar()->Hide();
108 LOG_INFO("Preferences", "Status bar hidden");
109 }
110 switch (windowSettings.startupMode) {
112 // Maximize window but keep menu bar and title bar visible
113 Maximize(true);
114 LOG_INFO("Preferences", "Starting in maximized mode");
115 break;
117 default:
118 LOG_INFO("Preferences", "Starting in normal windowed mode");
119 break;
120 }
121
122 // Apply window appearance settings
123 if (windowSettings.alwaysOnTop) {
124 SetWindowStyle(GetWindowStyle() | wxSTAY_ON_TOP);
125 LOG_INFO("Preferences", "Window set to always on top");
126 }
127
128 // Apply window size constraints (percentage of screen)
129 wxSize screen = EmberUI::DPI::GetScreenSize(this);
130 int minW = screen.x * windowSettings.minWindowWidthPct / 100;
131 int minH = screen.y * windowSettings.minWindowHeightPct / 100;
132 SetMinSize(wxSize(minW, minH));
133 LOG_INFO("Preferences", "Minimum window size set to " + std::to_string(minW) + "x" + std::to_string(minH) + " (" +
134 std::to_string(windowSettings.minWindowWidthPct) + "% x " +
135 std::to_string(windowSettings.minWindowHeightPct) + "% of screen)");
136
137 if (windowSettings.maxWindowWidthPct > 0 && windowSettings.maxWindowHeightPct > 0) {
138 int maxW = screen.x * windowSettings.maxWindowWidthPct / 100;
139 int maxH = screen.y * windowSettings.maxWindowHeightPct / 100;
140 SetMaxSize(wxSize(maxW, maxH));
141 LOG_INFO("Preferences", "Maximum window size set to " + std::to_string(maxW) + "x" + std::to_string(maxH) +
142 " (" + std::to_string(windowSettings.maxWindowWidthPct) + "% x " +
143 std::to_string(windowSettings.maxWindowHeightPct) + "% of screen)");
144 }
145
146 // Apply aspect ratio constraint (if enabled)
147 if (windowSettings.enforceAspectRatio) {
148 // Note: wxWidgets doesn't have built-in aspect ratio enforcement
149 // This would require custom resize handling in EVT_SIZE
150 LOG_INFO("Preferences", "Aspect ratio enforcement requested: " + std::to_string(windowSettings.aspectRatio) +
151 " (requires custom implementation)");
152 }
153
154 // Apply Main Panel settings on startup
155 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
156 if (m_scenePanel) {
157 // Apply canvas background color
158 wxColour bgColor(mainPanelSettings.canvasBackgroundColor.r, mainPanelSettings.canvasBackgroundColor.g,
159 mainPanelSettings.canvasBackgroundColor.b);
160 m_scenePanel->ApplyCanvasBackgroundColor(bgColor);
161
162 LOG_INFO("Preferences", "Main Panel settings applied on startup");
163 }
164
165 // Initialize performance monitoring
167 monitor.SetEnabled(true);
168 LOG_INFO("Performance", "Performance monitoring initialized in MainFrame");
169
170 // Auto-load last opened file if enabled in preferences
171 const auto &parserSettings = prefs.GetParserSettings();
172 if (parserSettings.autoLoadLastFile && !parserSettings.lastOpenedFilePath.empty()) {
173 wxString lastFilePath = wxString::FromUTF8(parserSettings.lastOpenedFilePath);
174 if (wxFileExists(lastFilePath)) {
175 LOG_INFO("Preferences", "Auto-loading last opened file: " + parserSettings.lastOpenedFilePath);
176 CallAfter([this, lastFilePath]() { LoadXMLFile(lastFilePath, false); });
177 } else {
178 LOG_WARNING("Preferences", "Last opened file no longer exists: " + parserSettings.lastOpenedFilePath);
179 }
180 }
181
182 // Set up welcome screen callback
183 if (m_scenePanel) {
184 m_scenePanel->SetWelcomeActionCallback([this](const std::string &action, const std::string &param) {
185 if (action == "new_project") {
186 NewProject();
187 } else if (action == "open_project") {
188 OpenProject();
189 } else if (action == "open_recent") {
190 OpenProject(wxString::FromUTF8(param));
191 } else if (action == "docs") {
192 wxLaunchDefaultBrowser("https://cloudflare-docs.ember-docs.pages.dev/");
193 }
194 });
195 }
196}
197
199 // Save panel states before cleanup
200 if (m_leftPanel) {
201 m_leftPanel->SaveState();
202 LOG_INFO("MainFrame", "Saved left panel state");
203 }
204
205 if (m_rightPanel) {
206 m_rightPanel->SaveState();
207 LOG_INFO("MainFrame", "Saved right panel state");
208 }
209
210 if (m_bottomPanel) {
211 m_bottomPanel->SaveState();
212 LOG_INFO("MainFrame", "Saved bottom panel state");
213 }
214
215 // Save panel visibility states for "RememberLast" option
216 if (m_auiManager) {
218
219 // Save individual panel visibility
220 wxAuiPaneInfo &leftPaneInfo = m_auiManager->GetPane("left_panel");
221 wxAuiPaneInfo &rightPaneInfo = m_auiManager->GetPane("right_panel");
222 wxAuiPaneInfo &bottomPaneInfo = m_auiManager->GetPane("bottom_panel");
223
224 auto &leftSettings = prefs.GetLeftPanelSettings();
225 auto &rightSettings = prefs.GetRightPanelSettings();
226 auto &bottomSettings = prefs.GetBottomPanelSettings();
227
228 leftSettings.lastPanelVisible = leftPaneInfo.IsShown();
229 rightSettings.lastPanelVisible = rightPaneInfo.IsShown();
230 bottomSettings.lastPanelVisible = bottomPaneInfo.IsShown();
231
232 LOG_INFO("MainFrame", wxString::Format("Saved panel visibility - left: %d, right: %d, bottom: %d",
233 leftSettings.lastPanelVisible, rightSettings.lastPanelVisible,
234 bottomSettings.lastPanelVisible)
235 .ToStdString());
236 }
237
238 // Save preferences to disk
240 std::string configPath = EmberForge::AppPreferences::GetDefaultConfigPath();
241 if (prefManager.GetPreferences().SaveToFile(configPath)) {
242 LOG_INFO("MainFrame", "Saved preferences to disk: " + configPath);
243 } else {
244 LOG_ERROR("MainFrame", "Failed to save preferences to disk");
245 }
246
247 // Clean up unified AUI manager
248 if (m_auiManager) {
249 m_auiManager->UnInit();
250 delete m_auiManager;
251 m_auiManager = nullptr;
252 LOG_INFO("AUI", "Cleaned up unified AUI manager");
253 }
254}
255
257 // Get hotkey settings from preferences
259 auto &prefs = prefsMgr.GetPreferences();
260 const auto &windowSettings = prefs.GetWindowSettings();
261 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
262 const auto &btViewSettings = prefs.GetBehaviorTreeViewSettings();
263
264 // Create menu bar
265 wxMenu *menuFile = new wxMenu;
266
267 // Project operations (at the top) with configured hotkeys
268 wxString newProjectLabel = wxString::Format("New &Project...\t%s", windowSettings.newProjectHotkey);
269 menuFile->Append(ID_NewProject, newProjectLabel, "Create a new BehaviorTree project");
270
271 wxString openProjectLabel = wxString::Format("Open Pro&ject...\t%s", windowSettings.openProjectHotkey);
272 menuFile->Append(ID_OpenProject, openProjectLabel, "Open an existing BehaviorTree project");
273
274 menuFile->Append(ID_CloseProject, "&Close Project", "Close the current project");
275 menuFile->Append(ID_SaveProject, "Save Project", "Save the current project");
276 menuFile->Append(ID_ProjectSettings, "Project Settings...", "Edit project settings");
277 menuFile->AppendSeparator();
278
279 // Single file operations with configured hotkeys
280 wxString openFileLabel = wxString::Format("&Open XML File...\t%s", windowSettings.openFileHotkey);
281 menuFile->Append(ID_LoadXML, openFileLabel, "Load a behavior tree from XML file");
282 menuFile->AppendSeparator();
283
284 wxString saveLabel = wxString::Format("&Save\t%s", windowSettings.saveHotkey);
285 menuFile->Append(ID_SaveXML, saveLabel, "Save current behavior tree to XML file");
286
287 wxString saveAsLabel = wxString::Format("Save &As...\t%s", windowSettings.saveAsHotkey);
288 menuFile->Append(ID_SaveAsXML, saveAsLabel, "Save behavior tree to new XML file");
289 menuFile->AppendSeparator();
290 menuFile->Append(wxID_EXIT);
291
292 wxMenu *menuHelp = new wxMenu;
293 menuHelp->Append(wxID_ABOUT);
294
295 // Create View menu with configured hotkeys
296 wxMenu *menuView = new wxMenu;
297
298 wxString maximizeLabel = wxString::Format("&Maximize\t%s", windowSettings.maximizeHotkey);
299 menuView->Append(ID_ToggleMaximize, maximizeLabel, "Toggle maximize window");
300
301 menuView->AppendSeparator();
302
303 // Scene navigation with configured hotkeys
304 wxString nextSceneLabel = wxString::Format("&Next Scene\t%s", mainPanelSettings.nextSceneHotkey);
305 menuView->Append(ID_NextScene, nextSceneLabel, "Switch to next scene");
306
307 wxString prevSceneLabel = wxString::Format("&Previous Scene\t%s", mainPanelSettings.previousSceneHotkey);
308 menuView->Append(ID_PreviousScene, prevSceneLabel, "Switch to previous scene");
309
310 menuView->AppendSeparator();
311
312 // Reset view control with configured hotkey
313 wxString resetViewLabel = wxString::Format("&Reset View\t%s", btViewSettings.resetViewHotkey);
314 menuView->Append(ID_ResetZoom, resetViewLabel, "Reset zoom and pan to default");
315
316 menuView->AppendSeparator();
317
318 // Reset UI option with configured hotkey
319 wxString resetUILabel = wxString::Format("Reset &UI\t%s", windowSettings.resetUIHotkey);
320 menuView->Append(ID_ResetUI, resetUILabel, "Reset all panels and preferences to defaults");
321
322 // Create Settings menu with configured hotkeys
323 wxMenu *menuSettings = new wxMenu;
324
325 wxString preferencesLabel = wxString::Format("&Preferences...\t%s", windowSettings.preferencesHotkey);
326 menuSettings->Append(ID_Preferences, preferencesLabel,
327 "Open the preferences dialog to configure EmberForge settings");
328
329 wxString parserConfigLabel = wxString::Format("&Parser Configuration...\t%s", windowSettings.parserConfigHotkey);
330 menuSettings->Append(ID_ParserConfig, parserConfigLabel,
331 "Configure XML parser behavior and create custom profiles");
332
333 wxMenuBar *menuBar = new wxMenuBar;
334 menuBar->Append(menuFile, "&File");
335 menuBar->Append(menuView, "&View"); // Add View menu
336 menuBar->Append(menuSettings, "&Settings"); // NEW: Add Settings menu
337 menuBar->Append(menuHelp, "&Help");
338
339 SetMenuBar(menuBar);
340
341 // Create status bar
342 CreateStatusBar();
343 SetStatusText("Welcome to EmberForge!");
344}
345
347 // Create toolbar at the top
348 m_panelToggleToolbar = CreateToolBar(wxTB_FLAT | wxTB_HORIZONTAL | wxTB_NODIVIDER, wxID_ANY);
349 m_panelToggleToolbar->SetToolBitmapSize(wxSize(22, 22));
350
351 // Note: Tree selector is now embedded in the BehaviorTreeScene canvas (top-right corner)
352
353 // Add a stretchable spacer to push buttons to the right
354 m_panelToggleToolbar->AddStretchableSpace();
355
356 // Load custom icons from resources folder
357 wxString resourcePath = EmberForge::ResourcePath::GetDir("buttons");
358
359 // Left panel button with all states
360 wxBitmap leftNormal(resourcePath + "left_panel/Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG);
361 wxBitmap leftHovered(resourcePath + "left_panel/Left_Panel_Hovered_22.png", wxBITMAP_TYPE_PNG);
362 wxBitmap leftPressed(resourcePath + "left_panel/Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG);
363 wxBitmap leftDisabled(resourcePath + "left_panel/Left_Panel_Disabled_22.png", wxBITMAP_TYPE_PNG);
364
365 wxBitmapButton *leftBtn = new wxBitmapButton(m_panelToggleToolbar, ID_ToggleLeftPanel, leftNormal,
366 wxDefaultPosition, wxSize(32, 32), wxBORDER_NONE | wxBU_EXACTFIT);
367 leftBtn->SetBitmapHover(leftHovered);
368 leftBtn->SetBitmapPressed(leftPressed);
369 leftBtn->SetBitmapDisabled(leftDisabled);
370 leftBtn->SetToolTip("Toggle Left Panel");
371 m_panelToggleToolbar->AddControl(leftBtn);
372
373 // Bottom panel button with all states
374 wxBitmap bottomNormal(resourcePath + "bottom_panel/Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG);
375 wxBitmap bottomHovered(resourcePath + "bottom_panel/Bottom_Panel_Hovered_22.png", wxBITMAP_TYPE_PNG);
376 wxBitmap bottomPressed(resourcePath + "bottom_panel/Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG);
377 wxBitmap bottomDisabled(resourcePath + "bottom_panel/Bottom_Panel_Disabled_22.png", wxBITMAP_TYPE_PNG);
378
379 wxBitmapButton *bottomBtn = new wxBitmapButton(m_panelToggleToolbar, ID_ToggleBottomPanel, bottomNormal,
380 wxDefaultPosition, wxSize(32, 32), wxBORDER_NONE | wxBU_EXACTFIT);
381 bottomBtn->SetBitmapHover(bottomHovered);
382 bottomBtn->SetBitmapPressed(bottomPressed);
383 bottomBtn->SetBitmapDisabled(bottomDisabled);
384 bottomBtn->SetToolTip("Toggle Bottom Panel");
385 m_panelToggleToolbar->AddControl(bottomBtn);
386
387 // Right panel button with all states
388 wxBitmap rightNormal(resourcePath + "right_panel/Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG);
389 wxBitmap rightHovered(resourcePath + "right_panel/Right_Panel_Hovered_22.png", wxBITMAP_TYPE_PNG);
390 wxBitmap rightPressed(resourcePath + "right_panel/Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG);
391 wxBitmap rightDisabled(resourcePath + "right_panel/Right_Panel_Disabled_22.png", wxBITMAP_TYPE_PNG);
392
393 wxBitmapButton *rightBtn = new wxBitmapButton(m_panelToggleToolbar, ID_ToggleRightPanel, rightNormal,
394 wxDefaultPosition, wxSize(32, 32), wxBORDER_NONE | wxBU_EXACTFIT);
395 rightBtn->SetBitmapHover(rightHovered);
396 rightBtn->SetBitmapPressed(rightPressed);
397 rightBtn->SetBitmapDisabled(rightDisabled);
398 rightBtn->SetToolTip("Toggle Right Panel");
399 m_panelToggleToolbar->AddControl(rightBtn);
400
401 m_panelToggleToolbar->Realize();
402
403 LOG_INFO("UI", "Panel toggle toolbar created with state-aware custom icons");
404}
405
407 // No menu initialization needed for preferences dialog approach
408 LOG_INFO("Config", "Configuration system initialized - use Settings > Preferences to configure");
409}
410
412 // Get updated hotkey settings from preferences
414 auto &prefs = prefsMgr.GetPreferences();
415 const auto &windowSettings = prefs.GetWindowSettings();
416 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
417 const auto &btViewSettings = prefs.GetBehaviorTreeViewSettings();
418
419 wxMenuBar *menuBar = GetMenuBar();
420 if (!menuBar)
421 return;
422
423 // Update File menu hotkeys
424 int fileMenuIndex = menuBar->FindMenu("File");
425 if (fileMenuIndex != wxNOT_FOUND) {
426 wxMenu *menuFile = menuBar->GetMenu(fileMenuIndex);
427 if (menuFile) {
428 wxMenuItem *newProjectItem = menuFile->FindItem(ID_NewProject);
429 if (newProjectItem) {
430 newProjectItem->SetItemLabel(wxString::Format("New &Project...\t%s", windowSettings.newProjectHotkey));
431 }
432
433 wxMenuItem *openProjectItem = menuFile->FindItem(ID_OpenProject);
434 if (openProjectItem) {
435 openProjectItem->SetItemLabel(
436 wxString::Format("Open Pro&ject...\t%s", windowSettings.openProjectHotkey));
437 }
438
439 wxMenuItem *openFileItem = menuFile->FindItem(ID_LoadXML);
440 if (openFileItem) {
441 openFileItem->SetItemLabel(wxString::Format("&Open XML File...\t%s", windowSettings.openFileHotkey));
442 }
443
444 wxMenuItem *saveItem = menuFile->FindItem(ID_SaveXML);
445 if (saveItem) {
446 saveItem->SetItemLabel(wxString::Format("&Save\t%s", windowSettings.saveHotkey));
447 }
448
449 wxMenuItem *saveAsItem = menuFile->FindItem(ID_SaveAsXML);
450 if (saveAsItem) {
451 saveAsItem->SetItemLabel(wxString::Format("Save &As...\t%s", windowSettings.saveAsHotkey));
452 }
453 }
454 }
455
456 // Update View menu hotkeys
457 int viewMenuIndex = menuBar->FindMenu("View");
458 if (viewMenuIndex != wxNOT_FOUND) {
459 wxMenu *menuView = menuBar->GetMenu(viewMenuIndex);
460 if (menuView) {
461 wxMenuItem *maximizeItem = menuView->FindItem(ID_ToggleMaximize);
462 if (maximizeItem) {
463 maximizeItem->SetItemLabel(wxString::Format("&Maximize\t%s", windowSettings.maximizeHotkey));
464 }
465
466 wxMenuItem *nextSceneItem = menuView->FindItem(ID_NextScene);
467 if (nextSceneItem) {
468 nextSceneItem->SetItemLabel(wxString::Format("&Next Scene\t%s", mainPanelSettings.nextSceneHotkey));
469 }
470
471 wxMenuItem *prevSceneItem = menuView->FindItem(ID_PreviousScene);
472 if (prevSceneItem) {
473 prevSceneItem->SetItemLabel(
474 wxString::Format("&Previous Scene\t%s", mainPanelSettings.previousSceneHotkey));
475 }
476
477 wxMenuItem *resetViewItem = menuView->FindItem(ID_ResetZoom);
478 if (resetViewItem) {
479 resetViewItem->SetItemLabel(wxString::Format("&Reset View\t%s", btViewSettings.resetViewHotkey));
480 }
481
482 wxMenuItem *resetUIItem = menuView->FindItem(ID_ResetUI);
483 if (resetUIItem) {
484 resetUIItem->SetItemLabel(wxString::Format("Reset &UI\t%s", windowSettings.resetUIHotkey));
485 }
486 }
487 }
488
489 // Update Settings menu hotkeys
490 int settingsMenuIndex = menuBar->FindMenu("Settings");
491 if (settingsMenuIndex != wxNOT_FOUND) {
492 wxMenu *menuSettings = menuBar->GetMenu(settingsMenuIndex);
493 if (menuSettings) {
494 wxMenuItem *preferencesItem = menuSettings->FindItem(ID_Preferences);
495 if (preferencesItem) {
496 preferencesItem->SetItemLabel(
497 wxString::Format("&Preferences...\t%s", windowSettings.preferencesHotkey));
498 }
499
500 wxMenuItem *parserConfigItem = menuSettings->FindItem(ID_ParserConfig);
501 if (parserConfigItem) {
502 parserConfigItem->SetItemLabel(
503 wxString::Format("&Parser Configuration...\t%s", windowSettings.parserConfigHotkey));
504 }
505 }
506 }
507
508 LOG_INFO("Preferences", "Menu hotkeys refreshed");
509}
510
512 // Refresh the canvas visualization to show tree structure changes
513 if (!m_scenePanel)
514 return;
515
516 auto activeScene = m_scenePanel->GetActiveScene();
517 if (!activeScene || activeScene->GetSceneType() != "BehaviorTree")
518 return;
519
520 auto *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
521 if (!btScene)
522 return;
523
524 auto *treeVis = btScene->GetTreeVisualization();
525 if (treeVis) {
526 auto currentTree = treeVis->GetTree();
527 if (currentTree) {
528 treeVis->SetTree(currentTree);
529 }
530
531 treeVis->RefreshCanvas();
532 treeVis->Refresh();
533 treeVis->Update();
534 }
535
536 btScene->Refresh();
537 m_scenePanel->Refresh();
538 m_scenePanel->Update();
539}
540
542 // Refresh menu hotkeys
544
545 // Apply preferences
547 const auto &windowSettings = prefs.GetWindowSettings();
548
549 // Apply status bar visibility
550 if (windowSettings.showStatusBar) {
551 GetStatusBar()->Show();
552 } else {
553 GetStatusBar()->Hide();
554 }
555
556 // Apply panel caption visibility
557 if (m_auiManager) {
558 wxAuiPaneInfo &leftPane = m_auiManager->GetPane("left_panel");
559 if (leftPane.IsOk()) {
560 leftPane.CaptionVisible(windowSettings.showPanelCaptions);
561 }
562
563 wxAuiPaneInfo &rightPane = m_auiManager->GetPane("right_panel");
564 if (rightPane.IsOk()) {
565 rightPane.CaptionVisible(windowSettings.showPanelCaptions);
566 }
567
568 wxAuiPaneInfo &bottomPane = m_auiManager->GetPane("bottom_panel");
569 if (bottomPane.IsOk()) {
570 bottomPane.CaptionVisible(windowSettings.showPanelCaptions);
571 }
572
573 wxAuiPaneInfo &scenePane = m_auiManager->GetPane("scene_panel");
574 if (scenePane.IsOk()) {
575 scenePane.CaptionVisible(windowSettings.showPanelCaptions);
576 }
577
578 m_auiManager->Update();
579 LOG_INFO("Preferences",
580 std::string("Panel captions ") + (windowSettings.showPanelCaptions ? "shown" : "hidden"));
581 }
582
583 // Apply always on top
584 long currentStyle = GetWindowStyle();
585 bool styleChanged = false;
586
587 if (windowSettings.alwaysOnTop) {
588 if (!(currentStyle & wxSTAY_ON_TOP)) {
589 SetWindowStyle(currentStyle | wxSTAY_ON_TOP);
590 LOG_INFO("Preferences", "Window set to always on top");
591 styleChanged = true;
592 }
593 } else {
594 if (currentStyle & wxSTAY_ON_TOP) {
595 SetWindowStyle(currentStyle & ~wxSTAY_ON_TOP);
596 LOG_INFO("Preferences", "Window always on top disabled");
597 styleChanged = true;
598 }
599 }
600
601 // If style changed, force a proper refresh
602 if (styleChanged) {
603 // Ensure menu bar accelerators are preserved
604 wxMenuBar *menuBar = GetMenuBar();
605 if (menuBar) {
606 // Detach and reattach menu bar to ensure accelerators work
607 SetMenuBar(nullptr);
608 SetMenuBar(menuBar);
609 }
610 Update();
611 }
612
613 // Apply Main Panel settings
614 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
615 if (m_scenePanel) {
616 // Apply canvas background color
617 wxColour bgColor(mainPanelSettings.canvasBackgroundColor.r, mainPanelSettings.canvasBackgroundColor.g,
618 mainPanelSettings.canvasBackgroundColor.b);
619 m_scenePanel->ApplyCanvasBackgroundColor(bgColor);
620
621 // Reload zoom/pan preferences for all scenes
622 int sceneCount = m_scenePanel->GetSceneCount();
623 for (int i = 0; i < sceneCount; i++) {
624 IScene *scene = m_scenePanel->GetScene(i);
625 if (scene) {
626 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(scene);
627 if (btScene) {
628 // Reload preferences in TreeVisualization
629 if (auto *treeVis = btScene->GetTreeVisualization()) {
630 treeVis->LoadPreferences();
631 LOG_INFO("Preferences",
632 wxString::Format("Reloaded zoom/pan preferences for scene %d", i).ToStdString());
633 }
634 }
635 }
636 }
637
638 LOG_INFO("Preferences", "Main Panel settings applied");
639 }
640
641 // Force layout refresh to account for status bar visibility change
642 SendSizeEvent();
643
644 LOG_INFO("Preferences", "Preferences refreshed and applied to UI");
645}
646
648 // ====== CREATE PANEL INSTANCES ======
649
650 // Create left panel container (parent is MainFrame now, not splitter)
651 m_leftPanel = new LeftSidePanel(this, this);
652
653 // Create main scene panel (center)
654 m_scenePanel = new MainPanel(this);
655
656 // Set up scene change callback to update hierarchy when switching scene tabs
657 m_scenePanel->SetSceneChangedCallback([this](IScene *newScene, int) {
658 if (!newScene || newScene->GetSceneType() != "BehaviorTree")
659 return;
660
661 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(newScene);
662 if (!btScene)
663 return;
664
665 // Update navigator with the new scene's tree adapter
666 if (m_navigatorTab) {
667 auto adapter = btScene->GetTreeAdapter();
668 if (adapter) {
669 m_navigatorTab->SetActiveTree(adapter, m_currentTreeId);
670 }
671 }
672 });
673
674 // Create right panel container
675 m_rightPanel = new RightSidePanel(this, this);
676
677 // Create unified bottom panel
678 m_bottomPanel = new BottomPanel(this, this);
679
680 // ====== FINISH PANEL SETUP (AFTER CONSTRUCTION) ======
681 // Call FinishSetup() after derived classes are fully constructed to ensure
682 // proper virtual dispatch for OnPanelSpecificSetup() which creates default tabs
683 m_leftPanel->FinishSetup();
684 m_rightPanel->FinishSetup();
685 m_bottomPanel->FinishSetup();
686
687 // ====== GET REFERENCES TO TABS ======
688 // Note: Tabs are now created by OnPanelSpecificSetup() and RestoreState()
689 // We just need to get references to specific tabs that MainFrame needs
690
691 m_propertiesTab = nullptr;
692 m_navigatorTab = nullptr;
693
694 // ====== SETUP AUI LAYOUT ======
695
696 // Get preferences for all panels
698 const auto &leftSettings = prefs.GetLeftPanelSettings();
699 const auto &rightSettings = prefs.GetRightPanelSettings();
700 const auto &bottomSettings = prefs.GetBottomPanelSettings();
701
702 // Determine initial visibility for left panel based on startup preference
703 bool showLeftPanel = true;
704 switch (leftSettings.panelStartupState) {
706 showLeftPanel = true;
707 break;
709 showLeftPanel = false;
710 break;
712 showLeftPanel = leftSettings.lastPanelVisible;
713 break;
714 }
715
716 // Get window settings for caption visibility
717 const auto &windowSettings = prefs.GetWindowSettings();
718
719 // Compute panel pixel sizes from percentages
720 wxSize cs = GetClientSize();
721 m_lastClientSize = cs;
722
723 int leftW = cs.x * leftSettings.defaultPanelWidthPct / 100;
724 int leftMinW = cs.x * leftSettings.minimumPanelWidthPct / 100;
725
726 // Add all panels to the unified AUI manager
727 m_auiManager->AddPane(m_leftPanel, wxAuiPaneInfo()
728 .Name("left_panel")
729 .Caption("Left Panel")
730 .CaptionVisible(windowSettings.showPanelCaptions)
731 .Left()
732 .BestSize(wxSize(leftW, -1))
733 .MinSize(wxSize(leftMinW, -1))
734 .Dockable(true)
735 .Floatable(false)
736 .CloseButton(false)
737 .Resizable(leftSettings.allowPanelCollapse)
738 .Show(showLeftPanel));
739
740 m_auiManager->AddPane(m_scenePanel, wxAuiPaneInfo()
741 .Name("scene_panel")
742 .Caption("Main Panel")
743 .CenterPane()
744 .CaptionVisible(windowSettings.showPanelCaptions)
745 .Dockable(true)
746 .Floatable(false)
747 .CloseButton(false)
748 .Resizable(true));
749
750 // Determine initial visibility for right panel based on startup preference
751 bool showRightPanel = true;
752 switch (rightSettings.panelStartupState) {
754 showRightPanel = true;
755 break;
757 showRightPanel = false;
758 break;
760 showRightPanel = rightSettings.lastPanelVisible;
761 break;
762 }
763
764 int rightW = cs.x * rightSettings.defaultPanelWidthPct / 100;
765 int rightMinW = cs.x * rightSettings.minimumPanelWidthPct / 100;
766
767 m_auiManager->AddPane(m_rightPanel, wxAuiPaneInfo()
768 .Name("right_panel")
769 .Caption("Right Panel")
770 .CaptionVisible(windowSettings.showPanelCaptions)
771 .Right()
772 .BestSize(wxSize(rightW, -1))
773 .MinSize(wxSize(rightMinW, -1))
774 .Dockable(true)
775 .Floatable(false)
776 .CloseButton(false)
777 .Resizable(rightSettings.allowPanelCollapse)
778 .Show(showRightPanel));
779
780 // Determine initial visibility for bottom panel based on startup preference
781 bool showBottomPanel = true;
782 switch (bottomSettings.panelStartupState) {
784 showBottomPanel = true;
785 break;
787 showBottomPanel = false;
788 break;
790 showBottomPanel = bottomSettings.lastPanelVisible;
791 break;
792 }
793
794 int bottomH = cs.y * bottomSettings.defaultPanelHeightPct / 100;
795 int bottomMinH = cs.y * bottomSettings.minimumPanelHeightPct / 100;
796
797 m_auiManager->AddPane(m_bottomPanel, wxAuiPaneInfo()
798 .Name("bottom_panel")
799 .Caption("Bottom Panel")
800 .CaptionVisible(windowSettings.showPanelCaptions)
801 .Bottom()
802 .BestSize(wxSize(-1, bottomH))
803 .MinSize(wxSize(-1, bottomMinH))
804 .Dockable(true)
805 .Floatable(false)
806 .CloseButton(false)
807 .Resizable(bottomSettings.allowPanelCollapse)
808 .Show(showBottomPanel));
809
810 // Track proportional panes for dynamic resizing
811 double leftProp = leftSettings.defaultPanelWidthPct / 100.0;
812 double leftMinProp = leftSettings.minimumPanelWidthPct / 100.0;
813 double rightProp = rightSettings.defaultPanelWidthPct / 100.0;
814 double rightMinProp = rightSettings.minimumPanelWidthPct / 100.0;
815 double bottomProp = bottomSettings.defaultPanelHeightPct / 100.0;
816 double bottomMinProp = bottomSettings.minimumPanelHeightPct / 100.0;
817
818 m_layout.Track("left_panel", leftProp, leftMinProp, true);
819 m_layout.Track("right_panel", rightProp, rightMinProp, true);
820 m_layout.Track("bottom_panel", bottomProp, bottomMinProp, false);
821
822 // Update the AUI layout
823 m_auiManager->Update();
824
825 // Apply panel preferences after AUI setup
826 m_leftPanel->ApplyPreferences();
827 m_leftPanel->RestoreState();
828
829 // Get reference to hierarchy/navigator tabs after they're created by RestoreState
830 auto *notebook = m_leftPanel->GetNotebook();
831 if (notebook) {
832 for (size_t i = 0; i < notebook->GetPageCount(); ++i) {
833 auto *page = notebook->GetPage(i);
834 auto *tab = dynamic_cast<ITab *>(page);
835 if (tab && tab->GetTabType() == "Navigator") {
836 auto *navTab = dynamic_cast<ForgeNavigatorTab *>(page);
837 if (navTab) {
839 LOG_INFO("MainFrame", "Found navigator tab reference");
840 }
841 }
842 }
843 }
844 // Also check right panel for navigator tab
845 if (auto *rightNb = m_rightPanel ? m_rightPanel->GetNotebook() : nullptr) {
846 for (size_t i = 0; i < rightNb->GetPageCount(); ++i) {
847 auto *page = rightNb->GetPage(i);
848 auto *tab = dynamic_cast<ITab *>(page);
849 if (tab && tab->GetTabType() == "Navigator") {
850 auto *navTab = dynamic_cast<ForgeNavigatorTab *>(page);
851 if (navTab) {
853 LOG_INFO("MainFrame", "Found navigator tab reference in right panel");
854 }
855 }
856 }
857 }
858
859 m_rightPanel->ApplyPreferences();
860 m_rightPanel->RestoreState();
861
862 // Get reference to properties tab after it's created by RestoreState
863 auto *rightNotebook = m_rightPanel->GetNotebook();
864 if (rightNotebook) {
865 for (size_t i = 0; i < rightNotebook->GetPageCount(); ++i) {
866 auto *page = rightNotebook->GetPage(i);
867 auto *tab = dynamic_cast<ITab *>(page);
868 if (tab && tab->GetTabType() == "Properties") {
869 m_propertiesTab = dynamic_cast<PropertiesTab *>(page);
870 LOG_INFO("MainFrame", "Found properties tab reference");
871 break;
872 }
873 }
874 }
875
876 m_bottomPanel->ApplyPreferences();
877 m_bottomPanel->RestoreState();
878
879 // Update button bitmaps to match initial panel visibility
881 wxString resourcePath = EmberForge::ResourcePath::GetDir("buttons");
882
883 // Update left panel button
884 wxWindow *leftWin = m_panelToggleToolbar->FindWindow(ID_ToggleLeftPanel);
885 if (leftWin) {
886 wxBitmapButton *leftBtn = dynamic_cast<wxBitmapButton *>(leftWin);
887 if (leftBtn) {
888 if (showLeftPanel) {
889 leftBtn->SetBitmap(
890 wxBitmap(resourcePath + "left_panel/Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
891 } else {
892 leftBtn->SetBitmap(
893 wxBitmap(resourcePath + "left_panel/Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
894 }
895 }
896 }
897
898 // Update bottom panel button
899 wxWindow *bottomWin = m_panelToggleToolbar->FindWindow(ID_ToggleBottomPanel);
900 if (bottomWin) {
901 wxBitmapButton *bottomBtn = dynamic_cast<wxBitmapButton *>(bottomWin);
902 if (bottomBtn) {
903 if (showBottomPanel) {
904 bottomBtn->SetBitmap(
905 wxBitmap(resourcePath + "bottom_panel/Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
906 } else {
907 bottomBtn->SetBitmap(
908 wxBitmap(resourcePath + "bottom_panel/Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
909 }
910 }
911 }
912
913 // Update right panel button
914 wxWindow *rightWin = m_panelToggleToolbar->FindWindow(ID_ToggleRightPanel);
915 if (rightWin) {
916 wxBitmapButton *rightBtn = dynamic_cast<wxBitmapButton *>(rightWin);
917 if (rightBtn) {
918 if (showRightPanel) {
919 rightBtn->SetBitmap(
920 wxBitmap(resourcePath + "right_panel/Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
921 } else {
922 rightBtn->SetBitmap(
923 wxBitmap(resourcePath + "right_panel/Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
924 }
925 }
926 }
927 }
928
929 LOG_INFO("AUI", wxString::Format(
930 "Created unified AUI layout - left panel: %d%% (min %d%%), visible=%d; right panel: "
931 "%d%% (min %d%%), visible=%d; bottom panel: %d%% (min %d%%), visible=%d",
932 leftSettings.defaultPanelWidthPct, leftSettings.minimumPanelWidthPct, showLeftPanel,
933 rightSettings.defaultPanelWidthPct, rightSettings.minimumPanelWidthPct, showRightPanel,
934 bottomSettings.defaultPanelHeightPct, bottomSettings.minimumPanelHeightPct, showBottomPanel)
935 .ToStdString());
936}
937
938// ====== PERFORMANCE MONITORING EVENT HANDLERS ======
939
940void MainFrame::OnPaint(wxPaintEvent &event) {
941 // Standard paint handling
942 event.Skip();
943}
944
945void MainFrame::OnIdle(wxIdleEvent &event) {
946 // Update system metrics periodically during idle time
947 static int idleCount = 0;
948 if (++idleCount % 100 == 0) { // Every 100 idle events
950 if (monitor.IsEnabled()) {
951 monitor.UpdateSystemMetrics();
952 }
953 }
954 event.Skip();
955}
956
957void MainFrame::OnFrameResize(wxSizeEvent &event) {
958 wxSize newSize = GetClientSize();
959 if (m_auiManager && m_lastClientSize.x > 0 && m_lastClientSize.y > 0) {
960 m_layout.HandleResize(m_auiManager, m_lastClientSize, newSize);
961 m_auiManager->Update();
962 }
963 m_lastClientSize = newSize;
964 event.Skip();
965}
966
967// ====== EVENT HANDLERS (SIMPLIFIED) ======
968
969void MainFrame::OnExit(wxCommandEvent &event) { Close(true); }
970
971void MainFrame::OnAbout(wxCommandEvent &event) {
972 wxMessageBox("This is a refactored wxWidgets application for EmberForge\n\n"
973 "Now with better code organization using separate panel classes!",
974 "About EmberForge", wxOK | wxICON_INFORMATION);
975}
976
977void MainFrame::OnLoadXML(wxCommandEvent &event) {
978 wxFileDialog openFileDialog(this, "Open Behavior Tree", "", "", "XML files (*.xml)|*.xml|All files (*.*)|*.*",
979 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
980
981 if (openFileDialog.ShowModal() == wxID_CANCEL)
982 return; // the user changed their mind
983
984 wxString filePath = openFileDialog.GetPath();
985 wxString ext = wxFileName(filePath).GetExt().Lower();
986
987 // Check if file is XML
988 if (ext != "xml") {
989 wxString message = wxString::Format("Unsupported File Format\n\n"
990 "File: %s\n"
991 "Extension: .%s\n\n"
992 "EmberForge currently only supports opening XML behavior tree files.\n"
993 "Please select an XML file (.xml) to open.",
994 wxFileName(filePath).GetFullName(), ext.IsEmpty() ? "none" : ext);
995
996 wxMessageBox(message, "Unsupported File Format", wxOK | wxICON_WARNING, this);
997 LOG_WARNING("FileOpen", std::string("Attempted to open unsupported file type: .") + ext.ToStdString());
998 return;
999 }
1000
1001 LoadXMLFile(filePath);
1002}
1003
1004void MainFrame::LoadXMLFile(const wxString &filePath, bool confirmOverride) {
1005 LOG_INFO("Parser", std::string("Loading XML file: ") + filePath.ToStdString());
1006
1007 // Variables to track user preferences for renaming
1008 bool shouldRename = false;
1009 wxString newSceneName;
1010
1011 // NOTE: OverrideSceneDialog is temporarily disabled
1012 // If confirmation is requested, show custom dialog with rename options
1013 // if (confirmOverride) {
1014 // wxFileName fileName(filePath);
1015 // EmberForge::OverrideSceneDialog confirmDialog(this, fileName.GetFullName());
1016 //
1017 // if (confirmDialog.ShowModal() != wxID_OK) {
1018 // LOG_INFO("Parser", "User cancelled XML loading");
1019 // return;
1020 // }
1021 //
1022 // // Get user preferences from dialog
1023 // shouldRename = confirmDialog.ShouldRename();
1024 // newSceneName = confirmDialog.GetSceneName();
1025 // }
1026 (void)confirmOverride; // Suppress unused parameter warning
1027
1028 // Create detailed loading dialog with scrollable log
1029 LoadingDialog loadingDialog(this, "Loading Behavior Tree (libxml2)");
1030 loadingDialog.SetMaxProgress(5);
1031 loadingDialog.Show(true);
1032 loadingDialog.AppendLog("Selected file: " + filePath, "[FILE]");
1033 wxYield(); // Force UI update
1034
1035 // Create a progress callback adapter
1036 class ProgressCallback : public EmberCore::IParseProgressCallback {
1037 private:
1038 LoadingDialog *dialog_;
1039
1040 public:
1041 explicit ProgressCallback(LoadingDialog *dialog) : dialog_(dialog) {}
1042
1043 bool OnProgress(const EmberCore::String &message, int current, int total) override {
1044 if (dialog_) {
1045 // Update progress with detailed message
1046 bool shouldContinue = dialog_->UpdateProgress(current, total, message);
1047
1048 // Add a small delay to make progress visible (especially for small files)
1049 wxMilliSleep(50);
1050
1051 return shouldContinue;
1052 }
1053 return true;
1054 }
1055
1056 // Additional method to append detailed log messages
1057 void AppendLog(const wxString &message, const wxString &prefix = "") {
1058 if (dialog_) {
1059 dialog_->AppendLog(message, prefix);
1060 }
1061 }
1062 };
1063
1064 ProgressCallback progressCallback(&loadingDialog);
1065
1066 // Get parser config from ConfigManager
1067 auto &configMgr = EmberCore::ConfigManager::GetInstance();
1068 auto activeProfile = configMgr.GetActiveProfile();
1069 auto parserConfig = activeProfile ? activeProfile->GetConfig() : EmberCore::ParserConfig::CreateDefault();
1070
1071 // Create libxml2 parser with active config and set progress callback
1072 EmberCore::LibXMLBehaviorTreeParser parser(parserConfig);
1073 parser.SetProgressCallback(&progressCallback);
1074
1075 // Add initial parsing logs
1076 progressCallback.AppendLog("libxml2 parser initialized", "[INIT]");
1077 progressCallback.AppendLog("Starting file parsing with libxml2...", "[INFO]");
1078
1079 // Load the XML with progress reporting
1080 auto behaviorTree = parser.ParseFromFile(filePath.ToStdString());
1081
1082 // Show completion
1083 if (!loadingDialog.WasCancelled()) {
1084 loadingDialog.UpdateProgress(5, 5, "Complete!");
1085 loadingDialog.AppendLog("libxml2 parsing completed successfully", "[COMPLETE]");
1086 wxMilliSleep(800); // Show completion message briefly
1087 }
1088
1089 // Mark as complete and keep dialog open for review
1090 loadingDialog.MarkComplete();
1091
1092 if (!behaviorTree) {
1093 // Show parsing errors in the dialog
1094 const auto &errors = parser.GetErrors();
1095 for (const auto &error : errors) {
1096 loadingDialog.AppendLog(wxString::FromUTF8(error.message), "[ERROR]");
1097 }
1098
1099 // Keep dialog open for user to review
1100 loadingDialog.ShowModal();
1101
1102 wxString errorMessage = "Failed to parse XML file with libxml2:\n\n";
1103 for (const auto &error : errors) {
1104 errorMessage += "• " + error.message + "\n";
1105 }
1106
1107 wxMessageBox(errorMessage, "libxml2 XML Parsing Error", wxOK | wxICON_ERROR);
1108 LOG_ERROR("Parser", "libxml2 XML parsing failed");
1109 return;
1110 }
1111
1112 // Keep dialog open for user to review logs before continuing
1113 loadingDialog.ShowModal();
1114
1115 LOG_INFO("Parser", "libxml2 XML parsed successfully, loading into scene view");
1116
1117 // Validate that the main tree has actual content (not just empty nodes)
1118 if (behaviorTree->HasRootNode()) {
1119 EmberCore::Node *rootNode = behaviorTree->GetRootNode();
1120 if (rootNode) {
1121 // Check if the root node has no children (likely due to skipped SubTree nodes)
1122 if (rootNode->GetChildCount() == 0) {
1123 wxMessageBox("Cannot load behavior tree: The main tree appears to be empty.\n\n"
1124 "This usually happens when the tree contains only SubTree nodes,\n"
1125 "which are not yet implemented in the parser.",
1126 "Empty Behavior Tree", wxOK | wxICON_WARNING);
1127 LOG_WARNING("Parser", "Refusing to load empty behavior tree - root node '" + rootNode->GetName() +
1128 "' has no children");
1129 return;
1130 }
1131 }
1132 }
1133
1134 // Get the scene panel and load the parsed tree
1135 if (m_scenePanel) {
1136 auto activeScene = m_scenePanel->GetActiveScene();
1137 if (activeScene) {
1138 auto treeVis = dynamic_cast<BehaviorTreeScene *>(activeScene);
1139 if (treeVis) {
1140 // Load the parsed tree into the scene
1141 // Create DirectTreeAdapter from the parsed BehaviorTree (same as old parser)
1142 auto directAdapter = std::make_shared<EmberCore::DirectTreeAdapter>(behaviorTree);
1143 treeVis->SetBehaviorTree(directAdapter);
1144
1145 // Update navigator with the new tree
1146 if (m_navigatorTab && directAdapter && directAdapter->HasRootNode()) {
1147 wxFileName fn(filePath);
1148 m_navigatorTab->SetActiveTree(directAdapter, fn.GetName().ToStdString());
1149 }
1150
1151 if (auto *visualization = treeVis->GetTreeVisualization()) {
1152 visualization->RefreshCanvas();
1153 visualization->Refresh();
1154 visualization->Update();
1155
1156 if (directAdapter && directAdapter->HasRootNode()) {
1157 CallAfter([visualization, directAdapter]() {
1158 visualization->CenterOnNode(directAdapter->GetRootNode());
1159 LOG_INFO("Parser", "Centered view on root node after loading XML (UniParser)");
1160 });
1161 }
1162 }
1163 treeVis->Refresh();
1164 m_scenePanel->Refresh();
1165 m_scenePanel->Update();
1166
1167 // Track the current file path for save operations
1168 m_currentFilePath = filePath;
1169 m_treeModified = false;
1170
1171 // Save the last opened file path to preferences
1172 auto &parserPrefs =
1174 parserPrefs.lastOpenedFilePath = filePath.ToStdString();
1177 LOG_INFO("Preferences", "Saved last opened file path: " + filePath.ToStdString());
1178
1179 // Update the scene title with the filename
1180 // NOTE: Custom rename via OverrideSceneDialog is temporarily disabled
1181 {
1182 wxFileName fileName(filePath);
1183 wxString sceneName = fileName.GetName();
1184 m_scenePanel->UpdateActiveSceneTitle(sceneName);
1185 }
1186 (void)shouldRename; // Suppress unused variable warning
1187 (void)newSceneName; // Suppress unused variable warning
1188
1189 m_scenePanel->ShowScenes();
1190
1191 LOG_INFO("Parser", "Behavior tree loaded into scene view");
1192 SetStatusText("Loaded with libxml2: " + wxFileName(filePath).GetName());
1193 } else {
1194 LOG_ERROR("Parser", "Active scene is not a behavior tree scene");
1195 wxMessageBox("Active scene is not a behavior tree scene", "Error", wxOK | wxICON_ERROR);
1196 }
1197 } else {
1198 LOG_ERROR("Parser", "No active scene available");
1199 wxMessageBox("No active scene available", "Error", wxOK | wxICON_ERROR);
1200 }
1201 } else {
1202 LOG_ERROR("Parser", "Scene panel not available");
1203 wxMessageBox("Scene panel not available", "Error", wxOK | wxICON_ERROR);
1204 }
1205}
1206
1207void MainFrame::LoadXMLFileInNewScene(const wxString &filePath) {
1208 if (!m_scenePanel) {
1209 LOG_ERROR("Parser", "Scene panel not available");
1210 wxMessageBox("Scene panel not available", "Error", wxOK | wxICON_ERROR);
1211 return;
1212 }
1213
1214 // Extract filename without extension to use as scene name
1215 wxFileName fileName(filePath);
1216 wxString sceneName = fileName.GetName();
1217
1218 // Create a new scene with the XML filename
1219 LOG_INFO("Parser", std::string("Creating new scene: ") + sceneName.ToStdString());
1220 int sceneIndex = m_scenePanel->CreateNewScene(sceneName);
1221
1222 if (sceneIndex < 0) {
1223 LOG_ERROR("Parser", "Failed to create new scene (limit may have been reached)");
1224 return;
1225 }
1226
1227 m_scenePanel->ShowScenes();
1228
1229 // The new scene is now active, so load the XML into it
1230 LoadXMLFile(filePath);
1231}
1232
1233void MainFrame::OnSaveXML(wxCommandEvent &event) {
1234 // If no current file path, redirect to Save As
1235 if (m_currentFilePath.IsEmpty()) {
1236 OnSaveAsXML(event);
1237 return;
1238 }
1239
1240 // Save to existing path
1242}
1243
1244void MainFrame::OnSaveAsXML(wxCommandEvent &event) {
1245 wxFileDialog saveFileDialog(this, "Save Behavior Tree XML", "", "", "XML files (*.xml)|*.xml",
1246 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
1247
1248 if (saveFileDialog.ShowModal() == wxID_CANCEL)
1249 return;
1250
1251 wxString filePath = saveFileDialog.GetPath();
1252 DoSaveXML(filePath);
1253}
1254
1255void MainFrame::DoSaveXML(const wxString &filePath) {
1256 LOG_INFO("Serializer", "Saving behavior tree to: " + filePath.ToStdString());
1257
1258 // 1. Get current behavior tree from scene
1259 auto behaviorTreeScene = dynamic_cast<BehaviorTreeScene *>(m_scenePanel->GetActiveScene());
1260 if (!behaviorTreeScene) {
1261 wxMessageBox("No behavior tree scene is currently active", "Save Error", wxOK | wxICON_ERROR);
1262 LOG_ERROR("Serializer", "No behavior tree scene is active");
1263 return;
1264 }
1265
1266 // Get the abstracted tree (ITreeStructure)
1267 auto treeStructure = behaviorTreeScene->GetAbstractedTree();
1268 if (!treeStructure) {
1269 wxMessageBox("No behavior tree is currently loaded", "Save Error", wxOK | wxICON_ERROR);
1270 LOG_ERROR("Serializer", "No behavior tree is loaded");
1271 return;
1272 }
1273
1274 // Cast to DirectTreeAdapter to access GetTree()
1275 auto adapter = dynamic_cast<EmberCore::DirectTreeAdapter *>(treeStructure);
1276 if (!adapter) {
1277 wxMessageBox("Unexpected tree adapter type", "Save Error", wxOK | wxICON_ERROR);
1278 LOG_ERROR("Serializer", "Tree adapter is not a DirectTreeAdapter");
1279 return;
1280 }
1281
1282 // 2. Get BehaviorTree from adapter
1283 std::shared_ptr<EmberCore::BehaviorTree> tree = adapter->GetTree();
1284 if (!tree) {
1285 wxMessageBox("Failed to access behavior tree data", "Save Error", wxOK | wxICON_ERROR);
1286 LOG_ERROR("Serializer", "Failed to get tree from adapter");
1287 return;
1288 }
1289
1290 // 3. Get parser config for validation
1291 auto &configMgr = EmberCore::ConfigManager::GetInstance();
1292 auto activeProfile = configMgr.GetActiveProfile();
1293 auto parserConfig = activeProfile ? activeProfile->GetConfig() : EmberCore::ParserConfig::CreateDefault();
1294
1295 // 4. Validate tree using unified validator
1296 EmberCore::BehaviorTreeValidator validator(parserConfig);
1297 auto validation = validator.Validate(tree.get());
1298
1299 // Build validation message with clear separation of errors and warnings
1300 bool hasIssues = validation.HasErrors() || validation.HasWarnings();
1301 if (hasIssues) {
1302 wxString msg;
1303
1304 // Show errors (during development, these are warnings that can be overridden)
1305 if (validation.HasErrors()) {
1306 msg += "⚠️ VALIDATION ERRORS (development mode allows saving):\n\n";
1307 for (const auto &issue : validation.issues) {
1309 wxString path = wxString::FromUTF8(issue.node_path.c_str());
1310 wxString message = wxString::FromUTF8(issue.message.c_str());
1311 if (!path.empty()) {
1312 msg += "• [" + path + "] " + message + "\n";
1313 } else {
1314 msg += "• " + message + "\n";
1315 }
1316 LOG_WARNING("Serializer", "Validation error: " + issue.message);
1317 }
1318 }
1319 msg += "\n";
1320 }
1321
1322 // Show warnings
1323 if (validation.HasWarnings()) {
1324 msg += "ℹ️ VALIDATION WARNINGS:\n\n";
1325 for (const auto &issue : validation.issues) {
1327 wxString path = wxString::FromUTF8(issue.node_path.c_str());
1328 wxString message = wxString::FromUTF8(issue.message.c_str());
1329 if (!path.empty()) {
1330 msg += "• [" + path + "] " + message + "\n";
1331 } else {
1332 msg += "• " + message + "\n";
1333 }
1334 LOG_INFO("Serializer", "Validation warning: " + issue.message);
1335 }
1336 }
1337 msg += "\n";
1338 }
1339
1340 msg += "These issues may cause problems when loading the file.\n";
1341 msg += "Save anyway? (Development workflow)";
1342
1343 wxString title = validation.HasErrors() ? "Validation Issues" : "Validation Warnings";
1344 int result = wxMessageBox(msg, title, wxYES_NO | wxICON_WARNING);
1345 if (result != wxYES) {
1346 LOG_INFO("Serializer", "Save cancelled by user due to validation issues");
1347 return;
1348 }
1349 }
1350
1351 // 5. Serialize using LibXMLBehaviorTreeSerializer
1352 EmberCore::LibXMLBehaviorTreeSerializer serializer(parserConfig);
1353 if (serializer.SerializeToFile(tree, filePath.ToStdString())) {
1354 m_currentFilePath = filePath;
1355 m_treeModified = false;
1356 SetStatusText("Saved: " + wxFileName(filePath).GetName());
1357 LOG_INFO("Serializer", "Tree saved successfully to: " + filePath.ToStdString());
1358
1359 wxMessageBox("Behavior tree saved successfully!", "Save Complete", wxOK | wxICON_INFORMATION);
1360 } else {
1361 // Show serialization errors
1362 wxString errorMessage = "Failed to save XML file:\n\n";
1363 const auto &errors = serializer.GetErrors();
1364 for (const auto &error : errors) {
1365 errorMessage += "• " + wxString::FromUTF8(error.message.c_str()) + "\n";
1366 }
1367
1368 wxMessageBox(errorMessage, "Save Error", wxOK | wxICON_ERROR);
1369 LOG_ERROR("Serializer", "Failed to save tree to: " + filePath.ToStdString());
1370 }
1371}
1372
1373// Settings menu event handlers
1374void MainFrame::OnPreferences(wxCommandEvent &event) {
1375 PreferencesDialog dialog(this);
1376 int result = dialog.ShowModal();
1377
1378 if (result == wxID_OK) {
1379 LOG_INFO("Config", "Preferences dialog closed with OK - settings applied");
1380 SetStatusText("Preferences updated");
1381
1382 // Apply preferences immediately
1384 } else {
1385 LOG_INFO("Config", "Preferences dialog cancelled");
1386 SetStatusText("Preferences cancelled");
1387 }
1388}
1389
1390void MainFrame::OnToggleLeftPanel(wxCommandEvent &event) {
1391 if (!m_auiManager)
1392 return;
1393
1394 wxAuiPaneInfo &pane = m_auiManager->GetPane("left_panel");
1395 if (pane.IsOk()) {
1396 pane.Show(!pane.IsShown());
1397 m_auiManager->Update();
1398
1399 // Update button bitmap to reflect toggle state
1400 wxBitmapButton *btn = dynamic_cast<wxBitmapButton *>(event.GetEventObject());
1401 if (btn) {
1402 wxString resourcePath = EmberForge::ResourcePath::GetDir("buttons/left_panel");
1403 if (pane.IsShown()) {
1404 btn->SetBitmap(wxBitmap(resourcePath + "Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1405 } else {
1406 btn->SetBitmap(wxBitmap(resourcePath + "Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1407 }
1408 }
1409
1410 LOG_INFO("UI", std::string("Left panel ") + (pane.IsShown() ? "shown" : "hidden"));
1411 SetStatusText(pane.IsShown() ? "Left panel shown" : "Left panel hidden");
1412 }
1413}
1414
1415void MainFrame::OnToggleBottomPanel(wxCommandEvent &event) {
1416 if (!m_auiManager)
1417 return;
1418
1419 wxAuiPaneInfo &pane = m_auiManager->GetPane("bottom_panel");
1420 if (pane.IsOk()) {
1421 pane.Show(!pane.IsShown());
1422 m_auiManager->Update();
1423
1424 // Update button bitmap to reflect toggle state
1425 wxBitmapButton *btn = dynamic_cast<wxBitmapButton *>(event.GetEventObject());
1426 if (btn) {
1427 wxString resourcePath = EmberForge::ResourcePath::GetDir("buttons/bottom_panel");
1428 if (pane.IsShown()) {
1429 btn->SetBitmap(wxBitmap(resourcePath + "Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1430 } else {
1431 btn->SetBitmap(wxBitmap(resourcePath + "Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1432 }
1433 }
1434
1435 LOG_INFO("UI", std::string("Bottom panel ") + (pane.IsShown() ? "shown" : "hidden"));
1436 SetStatusText(pane.IsShown() ? "Bottom panel shown" : "Bottom panel hidden");
1437 }
1438}
1439
1440void MainFrame::OnToggleRightPanel(wxCommandEvent &event) {
1441 if (!m_auiManager)
1442 return;
1443
1444 wxAuiPaneInfo &pane = m_auiManager->GetPane("right_panel");
1445 if (pane.IsOk()) {
1446 pane.Show(!pane.IsShown());
1447 m_auiManager->Update();
1448
1449 // Update button bitmap to reflect toggle state
1450 wxBitmapButton *btn = dynamic_cast<wxBitmapButton *>(event.GetEventObject());
1451 if (btn) {
1452 wxString resourcePath = EmberForge::ResourcePath::GetDir("buttons/right_panel");
1453 if (pane.IsShown()) {
1454 btn->SetBitmap(wxBitmap(resourcePath + "Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1455 } else {
1456 btn->SetBitmap(wxBitmap(resourcePath + "Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1457 }
1458 }
1459
1460 LOG_INFO("UI", std::string("Right panel ") + (pane.IsShown() ? "shown" : "hidden"));
1461 SetStatusText(pane.IsShown() ? "Right panel shown" : "Right panel hidden");
1462 }
1463}
1464
1467 return;
1468
1469 wxString resourcePath = EmberForge::ResourcePath::GetDir("buttons");
1470
1471 // Update left panel button
1472 wxAuiPaneInfo &leftPane = m_auiManager->GetPane("left_panel");
1473 wxWindow *leftWin = m_panelToggleToolbar->FindWindow(ID_ToggleLeftPanel);
1474 if (leftWin && leftPane.IsOk()) {
1475 wxBitmapButton *leftBtn = dynamic_cast<wxBitmapButton *>(leftWin);
1476 if (leftBtn) {
1477 if (leftPane.IsShown()) {
1478 leftBtn->SetBitmap(wxBitmap(resourcePath + "left_panel/Left_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1479 } else {
1480 leftBtn->SetBitmap(wxBitmap(resourcePath + "left_panel/Left_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1481 }
1482 }
1483 }
1484
1485 // Update bottom panel button
1486 wxAuiPaneInfo &bottomPane = m_auiManager->GetPane("bottom_panel");
1487 wxWindow *bottomWin = m_panelToggleToolbar->FindWindow(ID_ToggleBottomPanel);
1488 if (bottomWin && bottomPane.IsOk()) {
1489 wxBitmapButton *bottomBtn = dynamic_cast<wxBitmapButton *>(bottomWin);
1490 if (bottomBtn) {
1491 if (bottomPane.IsShown()) {
1492 bottomBtn->SetBitmap(
1493 wxBitmap(resourcePath + "bottom_panel/Bottom_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1494 } else {
1495 bottomBtn->SetBitmap(
1496 wxBitmap(resourcePath + "bottom_panel/Bottom_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1497 }
1498 }
1499 }
1500
1501 // Update right panel button
1502 wxAuiPaneInfo &rightPane = m_auiManager->GetPane("right_panel");
1503 wxWindow *rightWin = m_panelToggleToolbar->FindWindow(ID_ToggleRightPanel);
1504 if (rightWin && rightPane.IsOk()) {
1505 wxBitmapButton *rightBtn = dynamic_cast<wxBitmapButton *>(rightWin);
1506 if (rightBtn) {
1507 if (rightPane.IsShown()) {
1508 rightBtn->SetBitmap(
1509 wxBitmap(resourcePath + "right_panel/Right_Panel_Pressed_22.png", wxBITMAP_TYPE_PNG));
1510 } else {
1511 rightBtn->SetBitmap(
1512 wxBitmap(resourcePath + "right_panel/Right_Panel_Normal_22.png", wxBITMAP_TYPE_PNG));
1513 }
1514 }
1515 }
1516}
1517
1518int MainFrame::CountTabGlobally(const wxString &tabName) const {
1519 int count = 0;
1520 if (m_leftPanel && m_leftPanel->HasTabWithName(tabName)) {
1521 count++;
1522 }
1523 if (m_rightPanel && m_rightPanel->HasTabWithName(tabName)) {
1524 count++;
1525 }
1526 if (m_bottomPanel && m_bottomPanel->HasTabWithName(tabName)) {
1527 count++;
1528 }
1529 return count;
1530}
1531
1532void MainFrame::OnParserConfig(wxCommandEvent &event) {
1533 ParserConfigDialog dialog(this);
1534 int result = dialog.ShowModal();
1535
1536 if (result == wxID_OK) {
1537 LOG_INFO("Config", "Parser configuration updated");
1538 SetStatusText("Parser configuration saved");
1539 } else {
1540 LOG_INFO("Config", "Parser configuration cancelled");
1541 SetStatusText("Parser configuration cancelled");
1542 }
1543}
1544
1545// Tool button event handlers (delegated to panels)
1546void MainFrame::OnEditorTool(wxCommandEvent &event) {
1547 PERF_TIME_BEGIN(editor_tool_operation);
1548 LOG_INFO("Tool", "Editor Tool activated - Opening editor for BT configuration");
1549 PERF_TIME_END(editor_tool_operation);
1550}
1551
1552void MainFrame::OnMonitorTool(wxCommandEvent &event) {
1553 PERF_TIME_BEGIN(monitor_tool_operation);
1554 LOG_INFO("Tool", "Monitor Tool activated - Opening performance monitor");
1555 PERF_TIME_END(monitor_tool_operation);
1556}
1557
1558void MainFrame::OnLogReplayTool(wxCommandEvent &event) {
1559 PERF_TIME_BEGIN(log_replay_tool_operation);
1560 LOG_INFO("Tool", "Log Replay Tool activated - Replaying performance logs");
1561 PERF_TIME_END(log_replay_tool_operation);
1562}
1563
1564// View menu event handlers
1565void MainFrame::OnToggleMaximize(wxCommandEvent &event) {
1566 PERF_TIME_BEGIN(maximize_toggle);
1567 bool isMaximized = IsMaximized();
1568 if (!isMaximized) {
1569 Maximize(true);
1570 LOG_INFO("View", "Maximized window");
1571 } else {
1572 Maximize(false);
1573 LOG_INFO("View", "Restored window to normal size");
1574 }
1575 PERF_TIME_END(maximize_toggle);
1576}
1577
1578void MainFrame::OnNextScene(wxCommandEvent &event) {
1579 if (m_scenePanel) {
1580 m_scenePanel->SwitchToNextScene();
1581 }
1582}
1583
1584void MainFrame::OnPreviousScene(wxCommandEvent &event) {
1585 if (m_scenePanel) {
1586 m_scenePanel->SwitchToPreviousScene();
1587 }
1588}
1589
1590void MainFrame::OnResetZoom(wxCommandEvent &event) {
1591 if (m_scenePanel) {
1592 IScene *activeScene = m_scenePanel->GetActiveScene();
1593 if (activeScene) {
1594 // Try to cast to BehaviorTreeScene to access view reset functionality
1595 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
1596 if (btScene && btScene->GetTreeVisualization()) {
1597 // Reset view (zoom + pan to default)
1598 btScene->GetTreeVisualization()->ResetView();
1599
1601 float defaultZoom = prefs.GetMainPanelSettings().defaultZoomLevel;
1602 int zoomPercent = static_cast<int>(defaultZoom * 100);
1603
1604 SetStatusText(wxString::Format("View reset (zoom: %d%%)", zoomPercent));
1605 LOG_INFO("View", "View reset to default (zoom + pan)");
1606 }
1607 }
1608 }
1609}
1610
1611void MainFrame::OnResetUI(wxCommandEvent &event) {
1612 // Confirm with user
1613 int result = wxMessageBox("This will:\n"
1614 "- Close the current project (if open)\n"
1615 "- Reset all preferences to defaults\n"
1616 "- Reset all panels to their default tabs\n\n"
1617 "Are you sure you want to reset the UI?",
1618 "Reset UI", wxYES_NO | wxICON_WARNING, this);
1619
1620 if (result != wxYES) {
1621 return;
1622 }
1623
1624 LOG_INFO("UI", "Resetting UI to defaults");
1625
1626 // 1. Close the project if one is open
1627 if (m_activeProject) {
1628 CloseProject();
1629 LOG_INFO("UI", "Closed active project");
1630 }
1631
1632 // 2. Reset preferences to defaults
1634 EmberForge::AppPreferences defaultPrefs;
1635 prefsMgr.GetPreferences() = defaultPrefs;
1636
1637 std::string configPath = EmberForge::AppPreferences::GetDefaultConfigPath();
1638 prefsMgr.GetPreferences().SaveToFile(configPath);
1639 LOG_INFO("Preferences", "Reset preferences to defaults and saved to: " + configPath);
1640
1641 // 3. Reset panels - show them if hidden and set to default tabs only
1642 // Show all panels first
1643 if (m_auiManager) {
1644 wxAuiPaneInfo &leftPane = m_auiManager->GetPane("left_panel");
1645 if (leftPane.IsOk() && !leftPane.IsShown()) {
1646 leftPane.Show();
1647 }
1648
1649 wxAuiPaneInfo &rightPane = m_auiManager->GetPane("right_panel");
1650 if (rightPane.IsOk() && !rightPane.IsShown()) {
1651 rightPane.Show();
1652 }
1653
1654 wxAuiPaneInfo &bottomPane = m_auiManager->GetPane("bottom_panel");
1655 if (bottomPane.IsOk() && !bottomPane.IsShown()) {
1656 bottomPane.Show();
1657 }
1658
1659 m_auiManager->Update();
1660 }
1661
1662 // Reset each panel to its default tab
1663 // Phase 1: Clear all panels first to avoid global instance limit conflicts
1664 if (m_leftPanel) {
1665 m_leftPanel->ClearAllTabs();
1666 }
1667 if (m_rightPanel) {
1668 m_rightPanel->ClearAllTabs();
1669 }
1670 if (m_bottomPanel) {
1671 m_bottomPanel->ClearAllTabs();
1672 }
1673
1674 // Phase 2: Now create default tabs (global instance checks will pass)
1675 if (m_leftPanel) {
1676 m_leftPanel->ResetToDefaultTab(EmberForge::TabType::Navigator);
1677 }
1678 if (m_rightPanel) {
1680 }
1681 if (m_bottomPanel) {
1683 }
1684
1685 // 4. Update the visuals
1689
1690 // Force a full layout update
1691 if (m_auiManager) {
1692 m_auiManager->Update();
1693 }
1694 Layout();
1695 Refresh();
1696
1697 SetStatusText("UI reset to defaults");
1698 LOG_INFO("UI", "UI reset complete");
1699}
1700
1701void MainFrame::LogMessage(const wxString &message) {
1702 if (m_bottomPanel && m_bottomPanel->GetLogOutput()) {
1703 wxDateTime now = wxDateTime::Now();
1704 wxString timestamp = now.Format("%Y-%m-%d %H:%M:%S");
1705 wxString logEntry = wxString::Format("%s %s\n", timestamp, message);
1706
1707 m_bottomPanel->GetLogOutput()->AppendText(logEntry);
1708 m_bottomPanel->GetLogOutput()->SetInsertionPointEnd();
1709 }
1710}
1711
1713 // Called when user selects a node in Navigator's hierarchy view
1714 // Check if hierarchy → scene sync is enabled
1716 if (!config.GetSyncSettings().enableHierarchyToScene) {
1717 if (config.GetDebugSettings().enableSyncLogging) {
1718 LOG_INFO("Sync", "Hierarchy → Scene: Disabled by configuration");
1719 }
1720 return;
1721 }
1722
1723 // Prevent infinite loops
1725 if (config.GetDebugSettings().enableSyncLogging) {
1726 LOG_INFO("Sync", "Hierarchy → Scene: Skipped (already updating from scene)");
1727 }
1728 return;
1729 }
1730
1731 // Debug logging
1732 if (config.GetDebugSettings().enableSyncLogging) {
1733 LOG_INFO("Sync", "Hierarchy → Scene: Node selected in hierarchy tab");
1734 }
1735
1736 // Set flag to prevent loops
1738
1739 // Safety check: Ensure scene panel exists and is valid
1740 if (!m_scenePanel) {
1741 LOG_ERROR("Sync", "Scene panel is null");
1743 return;
1744 }
1745
1746 // Get the active scene and check if it's a behavior tree scene
1747 IScene *activeScene = m_scenePanel->GetActiveScene();
1748 if (!activeScene || activeScene->GetSceneType() != "BehaviorTree") {
1749 LOG_ERROR("Sync", "No active behavior tree scene");
1751 return;
1752 }
1753
1754 // Cast to BehaviorTreeScene to access tree visualization
1755 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
1756 if (!btScene) {
1757 LOG_ERROR("Sync", "Failed to cast to BehaviorTreeScene");
1759 return;
1760 }
1761
1762 auto *treeVis = btScene->GetTreeVisualization();
1763 if (!treeVis) {
1764 LOG_ERROR("Sync", "Tree visualization is null");
1766 return;
1767 }
1768
1769 // Additional safety: Check if the scene's panel is still valid
1770 wxPanel *scenePanel = btScene->GetPanel();
1771 if (!scenePanel || !scenePanel->IsShown()) {
1772 LOG_ERROR("Sync", "Scene panel is not shown");
1774 return;
1775 }
1776
1777 // Set the selected node in the scene view if highlighting is enabled
1778 // Note: Centering on node is NOT done here - it's only done on double-click
1779 // when the doubleClickAction preference is set to CenterInCanvas
1780 if (config.GetSyncSettings().enableSelectionHighlight) {
1781 if (config.GetDebugSettings().enableSyncLogging) {
1782 LOG_INFO("Sync", "Hierarchy → Scene: Calling treeVis->SetSelectedNode()");
1783 }
1784 treeVis->SetSelectedNode(selectedNode);
1785 }
1786
1787 // Update properties view
1788 if (m_propertiesTab) {
1789 if (selectedNode) {
1790 // Convert ITreeNode back to Node for properties tab
1791 auto nodeAdapter = dynamic_cast<const EmberCore::NodeAdapter *>(selectedNode);
1792 if (nodeAdapter) {
1793 EmberCore::Node *legacyNode = nodeAdapter->GetWrappedNode();
1794 if (config.GetDebugSettings().enableSyncLogging) {
1795 LOG_INFO("Sync", "Hierarchy → Properties: Updating properties with node: " + legacyNode->GetName());
1796 }
1797 m_propertiesTab->SetSelectedNode(legacyNode);
1798 }
1799 } else {
1800 if (config.GetDebugSettings().enableSyncLogging) {
1801 LOG_INFO("Sync", "Hierarchy → Properties: Clearing properties selection");
1802 }
1803 m_propertiesTab->ClearSelection();
1804 }
1805 }
1806
1807 if (config.GetDebugSettings().enableSyncLogging) {
1808 LOG_INFO("Sync", "Hierarchy → Scene: Sync operations completed");
1809 }
1810
1811 // Clear flag
1813}
1814
1816 // Check if scene → hierarchy sync is enabled
1818 if (!config.GetSyncSettings().enableSceneToHierarchy) {
1819 if (config.GetDebugSettings().enableSyncLogging) {
1820 LOG_INFO("Sync", "Scene → Hierarchy: Disabled by configuration");
1821 }
1822 return;
1823 }
1824
1825 // Prevent infinite loops
1827 if (config.GetDebugSettings().enableSyncLogging) {
1828 LOG_INFO("Sync", "Scene → Hierarchy: Skipped (already updating from hierarchy)");
1829 }
1830 return;
1831 }
1832
1833 // Debug logging
1834 if (config.GetDebugSettings().enableSyncLogging) {
1835 LOG_INFO("Sync", "Scene → Hierarchy: Node selected in scene view");
1836 }
1837
1838 // Set flag to prevent loops
1839 m_isUpdatingFromScene = true;
1840
1841 if (!m_navigatorTab) {
1842 m_isUpdatingFromScene = false;
1843 return;
1844 }
1845
1846 // Update Navigator hierarchy selection
1847 if (selectedNode) {
1848 m_navigatorTab->SelectNodeById(selectedNode->GetId());
1849 }
1850
1851 // Update properties view
1852 EmberCore::Node *legacyNode = nullptr;
1853 if (selectedNode) {
1854 auto nodeAdapter = dynamic_cast<const EmberCore::NodeAdapter *>(selectedNode);
1855 if (nodeAdapter)
1856 legacyNode = nodeAdapter->GetWrappedNode();
1857 }
1858 if (m_propertiesTab) {
1859 if (legacyNode) {
1860 LOG_INFO("Sync", "Scene → Properties: Updating properties with node: " + legacyNode->GetName());
1861 m_propertiesTab->SetSelectedNode(legacyNode);
1862 } else {
1863 LOG_INFO("Sync", "Scene → Properties: Clearing properties selection");
1864 m_propertiesTab->ClearSelection();
1865 }
1866 }
1867
1868 // Clear flag
1869 m_isUpdatingFromScene = false;
1870}
1871
1873 LOG_INFO("MainFrame", "PropertiesTab is being closed, nullifying pointer");
1874 m_propertiesTab = nullptr;
1875}
1876
1878 m_navigatorTab = navigatorTab;
1879
1880 if (m_navigatorTab) {
1881 if (!m_projectTrees.empty()) {
1882 m_navigatorTab->SetMainTreeId(m_mainTreeName);
1884 }
1885
1886 if (!m_projectBlackboards.empty()) {
1887 m_navigatorTab->SetBlackboards(m_projectBlackboards);
1888 }
1889
1890 if (m_scenePanel) {
1891 IScene *activeScene = m_scenePanel->GetActiveScene();
1892 if (activeScene && activeScene->GetSceneType() == "BehaviorTree") {
1893 auto *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
1894 if (btScene) {
1895 auto adapter = btScene->GetTreeAdapter();
1896 if (adapter) {
1897 m_navigatorTab->SetActiveTree(adapter, m_currentTreeId);
1898 }
1899 }
1900 }
1901 }
1902
1903 LOG_INFO("MainFrame", "Navigator tab reference updated and wired");
1904 }
1905}
1906
1907void MainFrame::NavigateToBlackboard(const std::string &bbId) {
1909 for (size_t i = 0; i < m_scenePanel->GetSceneCount(); ++i) {
1910 if (m_scenePanel->GetScene(static_cast<int>(i)) == m_blackboardScene) {
1911 m_scenePanel->SetActiveScene(static_cast<int>(i));
1912 break;
1913 }
1914 }
1915 m_blackboardScene->ScrollToBlackboard(bbId);
1916 }
1917}
1918
1920 LOG_INFO("MainFrame", "NavigatorTab is being closed, nullifying pointer");
1921 m_navigatorTab = nullptr;
1922}
1923
1924std::shared_ptr<EmberCore::Node> MainFrame::GetSharedBehaviorTree() const {
1925 if (!m_scenePanel) {
1926 return nullptr;
1927 }
1928
1929 // Get the active scene
1930 IScene *activeScene = m_scenePanel->GetActiveScene();
1931 if (!activeScene || activeScene->GetSceneType() != "BehaviorTree") {
1932 return nullptr;
1933 }
1934
1935 // Cast to BehaviorTreeScene to access the behavior tree
1936 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
1937 if (!btScene) {
1938 return nullptr;
1939 }
1940
1941 // Get the behavior tree root and wrap in shared_ptr
1942 EmberCore::Node *rawTree = btScene->GetBehaviorTree();
1943 if (!rawTree) {
1944 return nullptr;
1945 }
1946
1947 // Return as shared_ptr (the scene owns it, so we use a non-owning shared_ptr)
1948 return std::shared_ptr<EmberCore::Node>(rawTree, [](EmberCore::Node *) {});
1949}
1950
1952 m_propertiesTab = propertiesTab;
1953
1954 // If we have a properties tab, set the currently selected node (if any)
1956 IScene *activeScene = m_scenePanel->GetActiveScene();
1957 if (activeScene && activeScene->GetSceneType() == "BehaviorTree") {
1958 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
1959 if (btScene) {
1960 auto *treeVis = btScene->GetTreeVisualization();
1961 if (treeVis) {
1962 EmberCore::ITreeNode *selectedNode = treeVis->GetSelectedNode();
1963 if (selectedNode) {
1964 // Convert ITreeNode to Node for properties tab
1965 auto nodeAdapter = dynamic_cast<const EmberCore::NodeAdapter *>(selectedNode);
1966 if (nodeAdapter) {
1967 EmberCore::Node *legacyNode = nodeAdapter->GetWrappedNode();
1968 m_propertiesTab->SetSelectedNode(legacyNode);
1969 LOG_INFO("MainFrame", "Set selected node on newly created properties tab");
1970 }
1971 }
1972 }
1973 }
1974 }
1975 }
1976}
1977
1979 // Safety checks: Ensure scene panel and node are valid
1980 if (!m_scenePanel || !node) {
1981 return;
1982 }
1983
1984 // Get the active scene and check if it's a behavior tree scene
1985 IScene *activeScene = m_scenePanel->GetActiveScene();
1986 if (!activeScene || activeScene->GetSceneType() != "BehaviorTree") {
1987 return;
1988 }
1989
1990 // Cast to BehaviorTreeScene to access tree visualization
1991 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
1992 if (!btScene) {
1993 return;
1994 }
1995
1996 auto *treeVis = btScene->GetTreeVisualization();
1997 if (!treeVis) {
1998 return;
1999 }
2000
2001 // Additional safety: Check if the scene's panel is still valid
2002 wxPanel *scenePanel = btScene->GetPanel();
2003 if (!scenePanel || !scenePanel->IsShown()) {
2004 return;
2005 }
2006
2007 // Center the view on the selected node
2008 treeVis->CenterOnNode(node);
2009}
2010
2012 if (!m_scenePanel)
2013 return;
2014
2015 IScene *activeScene = m_scenePanel->GetActiveScene();
2016 if (!activeScene || activeScene->GetSceneType() != "BehaviorTree")
2017 return;
2018
2019 auto *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
2020 if (!btScene)
2021 return;
2022
2023 auto *treeVis = btScene->GetTreeVisualization();
2024 if (treeVis) {
2025 treeVis->InvalidateLayout();
2026 }
2027}
2028
2030 if (m_navigatorTab) {
2031 m_navigatorTab->RefreshHierarchy();
2032 }
2033}
2034
2036 // Prevent infinite loops between views
2037 static bool updating = false;
2038 if (updating) {
2039 LOG_INFO("Sync", "SyncSelection: Prevented infinite loop");
2040 return;
2041 }
2042 updating = true;
2043
2044 LOG_INFO("Sync", "SyncSelection: Synchronizing selection to: " + (selectedNode ? selectedNode->GetName() : "null"));
2045
2046 if (m_navigatorTab && selectedNode) {
2047 m_navigatorTab->SelectNodeById(selectedNode->GetId());
2048 }
2049
2050 // Update properties view
2051 if (m_propertiesTab) {
2052 if (selectedNode) {
2053 // Convert ITreeNode back to Node for properties tab
2054 auto nodeAdapter = dynamic_cast<const EmberCore::NodeAdapter *>(selectedNode);
2055 if (nodeAdapter) {
2056 EmberCore::Node *legacyNode = nodeAdapter->GetWrappedNode();
2057 LOG_INFO("Sync", "SyncSelection: Updating properties with node: " + legacyNode->GetName());
2058 m_propertiesTab->SetSelectedNode(legacyNode);
2059 }
2060 } else {
2061 LOG_INFO("Sync", "SyncSelection: Clearing properties selection");
2062 m_propertiesTab->ClearSelection();
2063 }
2064 }
2065
2066 // Update scene view
2067 if (m_scenePanel) {
2068 IScene *activeScene = m_scenePanel->GetActiveScene();
2069 if (activeScene && activeScene->GetSceneType() == "BehaviorTree") {
2070 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(activeScene);
2071 if (btScene && btScene->GetTreeVisualization()) {
2072 LOG_INFO("Sync", "SyncSelection: Updating scene view");
2073 btScene->GetTreeVisualization()->SetSelectedNode(selectedNode);
2074 }
2075 }
2076 }
2077
2078 updating = false;
2079 LOG_INFO("Sync", "SyncSelection: Synchronization completed");
2080}
2081
2082// ============================================================================
2083// Project Operations
2084// ============================================================================
2085
2086void MainFrame::OnNewProject(wxCommandEvent &event) { NewProject(); }
2087
2088void MainFrame::OnOpenProject(wxCommandEvent &event) { OpenProject(); }
2089
2090void MainFrame::OnCloseProject(wxCommandEvent &event) { CloseProject(); }
2091
2092void MainFrame::OnSaveProject(wxCommandEvent &event) { SaveProject(); }
2093
2094void MainFrame::OnProjectSettings(wxCommandEvent &event) {
2095 LOG_INFO("Menu", "OnProjectSettings called");
2097}
2098
2100 LOG_INFO("Project", "Creating new project...");
2101
2102 BehaviorTreeProjectDialog dialog(this, nullptr);
2103 int result = dialog.ShowModal();
2104
2105 if (result == wxID_OK) {
2106 auto project = dialog.GetProject();
2107 if (project) {
2108 // Get default my_projects directory
2109 wxString defaultProjectsDir = EmberForge::ResourcePath::GetDir("my_projects");
2110
2111 // Create directory if it doesn't exist
2112 if (!wxFileName::DirExists(defaultProjectsDir)) {
2113 wxFileName::Mkdir(defaultProjectsDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
2114 }
2115
2116 // Ask where to save the project
2117 wxFileDialog saveDialog(this, "Save BehaviorTree Project", defaultProjectsDir,
2118 wxString::FromUTF8(project->GetName()) + ".btproj",
2119 "BehaviorTree Project (*.btproj)|*.btproj", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
2120
2121 if (saveDialog.ShowModal() == wxID_OK) {
2122 wxString projectPath = saveDialog.GetPath();
2123 project->SetProjectFilePath(projectPath.ToStdString());
2124
2125 if (project->SaveToFile(projectPath.ToStdString())) {
2126 m_activeProject = project;
2127
2128 // Add to recent projects
2129 auto &projectManager = EmberCore::ProjectManager::GetInstance();
2130 projectManager.AddToRecentProjects(projectPath.ToStdString());
2131
2132 // Parse and load the project
2133 OpenProject(projectPath);
2134
2135 LOG_INFO("Project", "Project created and saved: " + projectPath.ToStdString());
2136 SetStatusText("Project created: " + wxString::FromUTF8(project->GetName()));
2137 } else {
2138 wxMessageBox("Failed to save project file.", "Error", wxOK | wxICON_ERROR, this);
2139 LOG_ERROR("Project", "Failed to save project file");
2140 }
2141 }
2142 }
2143 }
2144}
2145
2147 LOG_INFO("Project", "Opening project dialog...");
2148
2149 ProjectManagerDialog dialog(this);
2150 int result = dialog.ShowModal();
2151
2152 if (result == wxID_OK && dialog.HasSelectedProject()) {
2154 }
2155}
2156
2157void MainFrame::OpenProject(const wxString &projectPath) {
2158 LOG_INFO("Project", "Opening project: " + projectPath.ToStdString());
2159
2160 // Load the project
2161 auto project = std::make_shared<EmberCore::BehaviorTreeProject>();
2162 if (!project->LoadFromFile(projectPath.ToStdString())) {
2163 wxMessageBox("Failed to load project file.", "Error", wxOK | wxICON_ERROR, this);
2164 LOG_ERROR("Project", "Failed to load project from: " + projectPath.ToStdString());
2165 return;
2166 }
2167
2168 // Close any existing project
2169 if (m_activeProject) {
2170 CloseProject();
2171 }
2172
2173 m_activeProject = project;
2174
2175 // Add to recent projects
2176 auto &projectManager = EmberCore::ProjectManager::GetInstance();
2177 projectManager.AddToRecentProjects(projectPath.ToStdString());
2178
2179 // Get the parser profile
2180 auto &configManager = EmberCore::ConfigManager::GetInstance();
2181 auto profile = configManager.GetProfile(project->GetParserProfileName());
2182 if (!profile) {
2183 profile = configManager.GetActiveProfile();
2184 }
2185
2186 // Create parser and parse project files
2187 EmberCore::LibXMLBehaviorTreeParser parser(profile->GetConfig());
2188
2189 // Show loading dialog
2190 LoadingDialog loadingDialog(this, "Loading Project: " + wxString::FromUTF8(project->GetName()));
2191 loadingDialog.Show();
2192 wxYield();
2193
2194 // Progress callback to forward parser progress to the dialog
2195 class ProjectProgressCallback : public EmberCore::IParseProgressCallback {
2196 private:
2197 LoadingDialog *dialog_;
2198
2199 public:
2200 explicit ProjectProgressCallback(LoadingDialog *dialog) : dialog_(dialog) {}
2201
2202 bool OnProgress(const EmberCore::String &message, int current, int total) override {
2203 if (dialog_) {
2204 bool shouldContinue = dialog_->UpdateProgress(current, total, message);
2205 wxMilliSleep(30); // Small delay to make progress visible
2206 return shouldContinue;
2207 }
2208 return true;
2209 }
2210 };
2211
2212 ProjectProgressCallback progressCallback(&loadingDialog);
2213 parser.SetProgressCallback(&progressCallback);
2214
2215 // Log project information before parsing
2216 loadingDialog.AppendLog("Project: " + wxString::FromUTF8(project->GetName()), "[INFO]");
2217 loadingDialog.AppendLog("Parser Profile: " + wxString::FromUTF8(profile->GetName()), "[INFO]");
2218 loadingDialog.AppendLog("Resource Files: " + wxString::Format("%zu", project->GetResourceCount()), "[INFO]");
2219 loadingDialog.AppendLog("", ""); // Blank line
2220
2221 // List files to be parsed
2222 const auto &resources = project->GetResources();
2223 for (size_t i = 0; i < resources.size(); ++i) {
2224 wxFileName fn(wxString::FromUTF8(project->ResolveResourcePath(resources[i])));
2225 loadingDialog.AppendLog(wxString::Format(" %zu. %s", i + 1, fn.GetFullName()), "[FILE]");
2226 }
2227 loadingDialog.AppendLog("", ""); // Blank line
2228 loadingDialog.AppendLog("Starting parser...", "[INIT]");
2229 wxYield();
2230
2231 // Parse the project
2232 auto parseResult = parser.ParseProject(project.get());
2233
2234 // Log per-file parsing results
2235 loadingDialog.AppendLog("", ""); // Blank line
2236 loadingDialog.AppendLog("=== Per-File Parsing Results ===", "[INFO]");
2237
2238 int totalTrees = 0;
2239 int totalBlackboards = 0;
2240 int totalWarnings = 0;
2241 int totalErrors = 0;
2242
2243 for (const auto &fileInfo : parseResult.file_infos) {
2244 wxFileName fn(wxString::FromUTF8(fileInfo.filepath));
2245 wxString filename = fn.GetFullName();
2246
2247 if (fileInfo.parsed_successfully) {
2248 loadingDialog.AppendLog(filename + " - Parsed successfully", "[SUCCESS]");
2249 } else {
2250 loadingDialog.AppendLog(filename + " - Parse FAILED", "[ERROR]");
2251 }
2252
2253 // Show trees found
2254 if (fileInfo.tree_count > 0) {
2255 wxString treeList;
2256 for (size_t i = 0; i < fileInfo.tree_ids.size(); ++i) {
2257 if (i > 0)
2258 treeList += ", ";
2259 treeList += wxString::FromUTF8(fileInfo.tree_ids[i]);
2260 }
2261 loadingDialog.AppendLog(wxString::Format(" Trees: %d (%s)", fileInfo.tree_count, treeList), "[INFO]");
2262 totalTrees += fileInfo.tree_count;
2263 }
2264
2265 // Show blackboards found
2266 if (fileInfo.blackboard_count > 0) {
2267 wxString bbList;
2268 for (size_t i = 0; i < fileInfo.blackboard_ids.size(); ++i) {
2269 if (i > 0)
2270 bbList += ", ";
2271 bbList += wxString::FromUTF8(fileInfo.blackboard_ids[i]);
2272 }
2273 loadingDialog.AppendLog(wxString::Format(" Blackboards: %d (%s)", fileInfo.blackboard_count, bbList),
2274 "[INFO]");
2275 totalBlackboards += fileInfo.blackboard_count;
2276 }
2277
2278 // Show SubTree references
2279 if (!fileInfo.subtree_refs.empty()) {
2280 wxString refList;
2281 for (size_t i = 0; i < fileInfo.subtree_refs.size(); ++i) {
2282 if (i > 0)
2283 refList += ", ";
2284 refList += wxString::FromUTF8(fileInfo.subtree_refs[i]);
2285 }
2286 loadingDialog.AppendLog(" SubTree refs: " + refList, "[INFO]");
2287 }
2288
2289 // Show warnings
2290 for (const auto &warning : fileInfo.warnings) {
2291 loadingDialog.AppendLog(" " + wxString::FromUTF8(warning), "[WARN]");
2292 totalWarnings++;
2293 }
2294
2295 // Show errors
2296 for (const auto &error : fileInfo.errors) {
2297 loadingDialog.AppendLog(" " + wxString::FromUTF8(error), "[ERROR]");
2298 totalErrors++;
2299 }
2300 }
2301
2302 // Log project-level issues
2303 if (!parseResult.unimplemented_references.empty()) {
2304 loadingDialog.AppendLog("", "");
2305 loadingDialog.AppendLog("=== Unimplemented Tree References ===", "[WARN]");
2306 for (const auto &ref : parseResult.unimplemented_references) {
2307 loadingDialog.AppendLog(" " + wxString::FromUTF8(ref), "[WARN]");
2308 }
2309 }
2310
2311 if (!parseResult.duplicate_tree_ids.empty()) {
2312 loadingDialog.AppendLog("", "");
2313 loadingDialog.AppendLog("=== Duplicate Tree IDs ===", "[WARN]");
2314 for (const auto &dup : parseResult.duplicate_tree_ids) {
2315 loadingDialog.AppendLog(" " + wxString::FromUTF8(dup), "[WARN]");
2316 }
2317 }
2318
2319 if (!parseResult.duplicate_blackboard_ids.empty()) {
2320 loadingDialog.AppendLog("", "");
2321 loadingDialog.AppendLog("=== Duplicate Blackboard IDs ===", "[WARN]");
2322 for (const auto &dup : parseResult.duplicate_blackboard_ids) {
2323 loadingDialog.AppendLog(" " + wxString::FromUTF8(dup), "[WARN]");
2324 }
2325 }
2326
2327 if (!parseResult.unresolved_blackboard_includes.empty()) {
2328 loadingDialog.AppendLog("", "");
2329 loadingDialog.AppendLog("=== Unresolved Blackboard Includes ===", "[WARN]");
2330 for (const auto &unres : parseResult.unresolved_blackboard_includes) {
2331 loadingDialog.AppendLog(" " + wxString::FromUTF8(unres), "[WARN]");
2332 }
2333 }
2334
2335 // Summary
2336 loadingDialog.AppendLog("", "");
2337 loadingDialog.AppendLog("=== Summary ===", "[INFO]");
2338 loadingDialog.AppendLog(wxString::Format("Files parsed: %zu", parseResult.file_infos.size()), "[INFO]");
2339 loadingDialog.AppendLog(wxString::Format("Total trees: %d", totalTrees), "[INFO]");
2340 loadingDialog.AppendLog(wxString::Format("Total blackboards: %d", totalBlackboards), "[INFO]");
2341 if (totalWarnings > 0) {
2342 loadingDialog.AppendLog(wxString::Format("Warnings: %d", totalWarnings), "[WARN]");
2343 }
2344 if (totalErrors > 0) {
2345 loadingDialog.AppendLog(wxString::Format("Errors: %d", totalErrors), "[ERROR]");
2346 }
2347
2348 wxYield();
2349
2350 if (parseResult.success) {
2351 // Update project with tree statuses
2352 project->SetTreeImplementationStatus(parseResult.tree_statuses);
2353
2354 // Store ALL parsed trees (not just main)
2355 m_projectTrees = parseResult.parsed_trees;
2356 m_mainTreeName = parseResult.main_tree_name;
2357
2358 // Store blackboard data
2359 m_projectBlackboards = parseResult.parsed_blackboards;
2360 m_blackboardIncludesMap = parseResult.blackboard_includes_map;
2361
2362 // Create the non-closable BlackboardScene at tab 0
2363 if (m_scenePanel && !m_projectBlackboards.empty()) {
2364 auto bbScene = std::make_unique<BlackboardScene>(m_scenePanel->GetNotebook());
2365 bbScene->SetBlackboards(m_projectBlackboards, m_blackboardIncludesMap);
2366 m_blackboardScene = bbScene.get();
2367 m_scenePanel->InsertScene(0, std::move(bbScene));
2368 }
2369
2370 // Pass blackboards to navigator
2371 if (m_navigatorTab && !m_projectBlackboards.empty()) {
2372 m_navigatorTab->SetBlackboards(m_projectBlackboards);
2373 }
2374
2375 // Check if there's a valid main tree entry point
2376 bool hasMainTreeEntryPoint = !parseResult.main_tree_name.empty() &&
2377 m_projectTrees.find(parseResult.main_tree_name) != m_projectTrees.end();
2378
2379 if (hasMainTreeEntryPoint) {
2380 // Found main tree entry point - load it normally
2381 LOG_INFO("Project", "Found main tree entry point: " + parseResult.main_tree_name);
2382 loadingDialog.AppendLog("Main tree: " + wxString::FromUTF8(parseResult.main_tree_name), "[INFO]");
2383 LoadTreeIntoScene(parseResult.main_tree_name);
2384 } else if (!m_projectTrees.empty()) {
2385 // No main tree entry point found
2386 // FIXED: Skip expensive overview scene creation for large projects (>20 trees)
2387 // Instead, user can select trees from the dropdown
2388 if (m_projectTrees.size() > 20) {
2389 LOG_INFO("Project", "No main tree entry point found - project has " +
2390 std::to_string(m_projectTrees.size()) +
2391 " trees. Use tree selector to view individual trees.");
2392 loadingDialog.AppendLog("No main tree - loading first tree", "[INFO]");
2393
2394 // Load the first tree as a starting point
2395 if (!m_projectTrees.empty()) {
2396 LoadTreeIntoScene(m_projectTrees.begin()->first);
2397 }
2398 } else {
2399 // Small project - create overview scene with all trees side-by-side
2400 LOG_INFO("Project", "No main tree entry point found - creating project overview with all trees");
2401 loadingDialog.AppendLog("No main tree - creating project overview", "[INFO]");
2403 }
2404 }
2405
2406 // Update tree selector dropdown
2408
2409 if (m_scenePanel)
2410 m_scenePanel->ShowScenes();
2411
2412 loadingDialog.AppendLog("", "");
2413 loadingDialog.AppendLog("Project loaded successfully!", "[COMPLETE]");
2414 loadingDialog.UpdateProgress(100, 100, "Project loaded successfully");
2415 wxYield();
2416
2417 loadingDialog.MarkComplete();
2418
2419 LOG_INFO("Project", "Project loaded successfully: " + project->GetName() + " with " +
2420 std::to_string(m_projectTrees.size()) + " trees");
2421 SetStatusText("Project: " + wxString::FromUTF8(project->GetName()) + " (" +
2422 wxString::Format("%zu", m_projectTrees.size()) + " trees)");
2423
2424 loadingDialog.ShowModal();
2425 } else {
2426 // Mark as complete even on error so user can review logs
2427 loadingDialog.AppendLog("Project parsing failed", "[ERROR]");
2428
2429 wxString errorMsg = "Failed to parse project files:\n\n";
2430 for (const auto &err : parseResult.errors) {
2431 errorMsg += "- " + wxString::FromUTF8(err.message) + "\n";
2432 }
2433
2434 // Log errors to the dialog
2435 for (const auto &err : parseResult.errors) {
2436 loadingDialog.AppendLog(wxString::FromUTF8(err.message), "[ERROR]");
2437 }
2438
2439 loadingDialog.MarkComplete();
2440
2441 LOG_ERROR("Project", "Failed to parse project files");
2442
2443 // Keep dialog open until user closes it
2444 loadingDialog.ShowModal();
2445
2446 wxMessageBox(errorMsg, "Error", wxOK | wxICON_ERROR, this);
2447
2448 m_activeProject = nullptr;
2449 m_projectTrees.clear();
2450 m_currentTreeId.clear();
2451 }
2452}
2453
2454void MainFrame::LoadTreeIntoScene(const std::string &treeId) {
2455 if (m_projectTrees.find(treeId) == m_projectTrees.end()) {
2456 LOG_WARNING("MainFrame", "Tree not found: " + treeId);
2457 return;
2458 }
2459
2460 auto tree = m_projectTrees[treeId];
2461 m_currentTreeId = treeId;
2462
2463 if (!tree || !m_scenePanel)
2464 return;
2465
2466 IScene *scene = m_scenePanel->GetActiveScene();
2467 BehaviorTreeScene *btScene = nullptr;
2468
2469 if (scene && scene->GetSceneType() == "BehaviorTree") {
2470 btScene = dynamic_cast<BehaviorTreeScene *>(scene);
2471 }
2472
2473 if (!btScene) {
2474 int idx = m_scenePanel->CreateNewScene(wxString::FromUTF8(treeId));
2475 if (idx < 0)
2476 return;
2477 scene = m_scenePanel->GetScene(idx);
2478 if (scene && scene->GetSceneType() == "BehaviorTree") {
2479 btScene = dynamic_cast<BehaviorTreeScene *>(scene);
2480 if (btScene) {
2481 btScene->SetNodeSelectionCallback(
2482 [this](EmberCore::ITreeNode *node) { OnSceneSelectionChanged(node); });
2483 if (auto *treeVis = btScene->GetTreeVisualization()) {
2484 treeVis->SetVisibilityChangeCallback([this]() { RefreshHierarchyTree(); });
2485 }
2486 }
2487 }
2488 }
2489
2490 if (!btScene)
2491 return;
2492
2493 EmberCore::Node *rootNodePtr = tree->GetRootNode();
2494 if (rootNodePtr) {
2495 std::shared_ptr<EmberCore::Node> rootNode(tree, rootNodePtr);
2496 btScene->SetBehaviorTree(rootNode);
2497
2498 if (m_navigatorTab) {
2499 auto adapter = btScene->GetTreeAdapter();
2500 if (adapter) {
2501 m_navigatorTab->SetActiveTree(adapter, treeId);
2502 }
2503 }
2504
2505 SetStatusText("Viewing tree: " + wxString::FromUTF8(treeId));
2506 LOG_INFO("MainFrame", "Loaded tree into scene: " + treeId);
2507 }
2508}
2509
2511 if (!m_scenePanel)
2512 return false;
2513 IScene *scene = m_scenePanel->GetActiveScene();
2514 return scene && scene->GetSceneType() == "BehaviorTree";
2515}
2516
2517void MainFrame::OpenTreeInNewScene(const std::string &treeId) {
2518 if (m_projectTrees.find(treeId) == m_projectTrees.end()) {
2519 LOG_WARNING("MainFrame", "Tree not found: " + treeId);
2520 return;
2521 }
2522
2523 if (!m_scenePanel) {
2524 LOG_ERROR("MainFrame", "Scene panel not available");
2525 return;
2526 }
2527
2528 auto tree = m_projectTrees[treeId];
2529 if (!tree)
2530 return;
2531
2532 int sceneIdx = m_scenePanel->CreateNewScene(wxString::FromUTF8(treeId));
2533 if (sceneIdx < 0)
2534 return;
2535
2536 IScene *scene = m_scenePanel->GetScene(sceneIdx);
2537 if (!scene || scene->GetSceneType() != "BehaviorTree")
2538 return;
2539
2540 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(scene);
2541 if (!btScene)
2542 return;
2543
2544 EmberCore::Node *rootNodePtr = tree->GetRootNode();
2545 if (rootNodePtr) {
2546 std::shared_ptr<EmberCore::Node> rootNode(tree, rootNodePtr);
2547 btScene->SetBehaviorTree(rootNode);
2548
2550
2551 if (auto *treeVis = btScene->GetTreeVisualization()) {
2552 treeVis->SetVisibilityChangeCallback([this]() { RefreshHierarchyTree(); });
2553 }
2554 }
2555
2556 m_scenePanel->SetActiveScene(sceneIdx);
2557 m_currentTreeId = treeId;
2559 SetStatusText("Viewing tree: " + wxString::FromUTF8(treeId));
2560 LOG_INFO("MainFrame", "Opened tree '" + treeId + "' in new scene");
2561}
2562
2564 if (!m_scenePanel || m_projectTrees.empty()) {
2565 return;
2566 }
2567
2568 LOG_INFO("MainFrame", "Creating project overview scene with " + std::to_string(m_projectTrees.size()) + " trees");
2569
2570 IScene *scene = m_scenePanel->GetActiveScene();
2571 BehaviorTreeScene *btScene = nullptr;
2572
2573 if (scene && scene->GetSceneType() == "BehaviorTree") {
2574 btScene = dynamic_cast<BehaviorTreeScene *>(scene);
2575 }
2576
2577 if (!btScene) {
2578 int idx = m_scenePanel->CreateNewScene("Project Overview");
2579 if (idx < 0)
2580 return;
2581 scene = m_scenePanel->GetScene(idx);
2582 if (scene && scene->GetSceneType() == "BehaviorTree")
2583 btScene = dynamic_cast<BehaviorTreeScene *>(scene);
2584 }
2585
2586 if (!btScene) {
2587 return;
2588 }
2589
2590 auto *treeVis = btScene->GetTreeVisualization();
2591 if (!treeVis) {
2592 return;
2593 }
2594
2595 // Create a scrolled window to hold all trees
2596 wxScrolled<wxPanel> *overviewPanel = new wxScrolled<wxPanel>(btScene->GetPanel(), wxID_ANY);
2597 overviewPanel->SetBackgroundColour(wxColour(45, 45, 50));
2598 overviewPanel->SetScrollRate(20, 20);
2599
2600 // Use horizontal box sizer to arrange trees side-by-side
2601 wxBoxSizer *horizontalSizer = new wxBoxSizer(wxHORIZONTAL);
2602
2603 // Create a tree visualization for each tree
2604 size_t treeIndex = 0;
2605 for (const auto &pair : m_projectTrees) {
2606 const std::string &treeId = pair.first;
2607 auto tree = pair.second;
2608
2609 EmberCore::Node *rootNodePtr = tree->GetRootNode();
2610 if (!rootNodePtr) {
2611 continue;
2612 }
2613
2614 // Create a panel for this tree
2615 wxPanel *treePanel = new wxPanel(overviewPanel, wxID_ANY);
2616 treePanel->SetBackgroundColour(wxColour(45, 45, 50));
2617 wxBoxSizer *treeSizer = new wxBoxSizer(wxVERTICAL);
2618
2619 // Add tree label
2620 wxStaticText *label = new wxStaticText(treePanel, wxID_ANY, wxString::FromUTF8(treeId));
2621 wxFont labelFont = label->GetFont();
2622 labelFont.SetPointSize(labelFont.GetPointSize() + 2);
2623 labelFont.MakeBold();
2624 label->SetFont(labelFont);
2625 label->SetForegroundColour(wxColour(200, 200, 200));
2626 treeSizer->Add(label, 0, wxALL | wxALIGN_CENTER, 10);
2627
2628 auto *vis = new EmberForge::ForgeTreeCanvas(treePanel);
2629 vis->SetMinSize(wxSize(600, 400));
2630
2631 auto behaviorTree = std::make_shared<EmberCore::BehaviorTree>(treeId);
2632 std::shared_ptr<EmberCore::Node> rootNode(tree, rootNodePtr);
2633 behaviorTree->SetRootNode(rootNode);
2634 auto treeAdapter = std::make_shared<EmberCore::DirectTreeAdapter>(behaviorTree);
2635 vis->SetTree(treeAdapter);
2636
2637 treeSizer->Add(vis, 1, wxEXPAND | wxALL, 5);
2638 treePanel->SetSizer(treeSizer);
2639
2640 // Add to horizontal layout with separator
2641 horizontalSizer->Add(treePanel, 0, wxEXPAND | wxALL, 10);
2642
2643 if (treeIndex < m_projectTrees.size() - 1) {
2644 // Add vertical separator line
2645 wxPanel *separator = new wxPanel(overviewPanel, wxID_ANY, wxDefaultPosition, wxSize(2, -1));
2646 separator->SetBackgroundColour(wxColour(80, 80, 85));
2647 horizontalSizer->Add(separator, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
2648 }
2649
2650 treeIndex++;
2651 }
2652
2653 overviewPanel->SetSizer(horizontalSizer);
2654 overviewPanel->FitInside(); // Adjust scrollbar
2655
2656 // Replace the tree visualization with the overview panel
2657 wxSizer *panelSizer = btScene->GetPanel()->GetSizer();
2658 if (panelSizer) {
2659 panelSizer->Clear(false); // Don't delete children yet
2660 panelSizer->Add(overviewPanel, 1, wxEXPAND | wxALL, 5);
2661 btScene->GetPanel()->Layout();
2662 }
2663
2664 SetStatusText("Project Overview: " + wxString::Format("%zu trees", m_projectTrees.size()));
2665 LOG_INFO("MainFrame", "Project overview scene created successfully");
2666}
2667
2669 if (m_navigatorTab) {
2670 m_navigatorTab->SetMainTreeId(m_mainTreeName);
2672 }
2673}
2674
2676 if (!m_activeProject) {
2677 LOG_INFO("Project", "No project to close");
2678 return;
2679 }
2680
2681 LOG_INFO("Project", "Closing project: " + m_activeProject->GetName());
2682
2683 // TODO: Check for unsaved changes and prompt
2684
2685 // Clear the scene and tree selector
2686 if (m_scenePanel) {
2687 IScene *scene = m_scenePanel->GetActiveScene();
2688 if (scene && scene->GetSceneType() == "BehaviorTree") {
2689 BehaviorTreeScene *btScene = dynamic_cast<BehaviorTreeScene *>(scene);
2690 if (btScene) {
2691 btScene->SetBehaviorTree(std::shared_ptr<EmberCore::Node>(nullptr)); // Clear the tree from the scene
2692
2693 auto *treeViz = btScene->GetTreeVisualization();
2694 if (treeViz) {
2695 treeViz->SetTree(nullptr);
2696 treeViz->RefreshCanvas();
2697 }
2698 }
2699 }
2700 }
2701
2702 // Clear the properties tab
2703 if (m_propertiesTab) {
2704 m_propertiesTab->ClearSelection();
2705 }
2706
2707 // Clear navigator tab
2708 if (m_navigatorTab) {
2709 m_navigatorTab->ClearTreeList();
2710 }
2711
2712 // Remove the blackboard scene if it exists
2714 for (size_t i = 0; i < m_scenePanel->GetSceneCount(); ++i) {
2715 if (m_scenePanel->GetScene(static_cast<int>(i)) == m_blackboardScene) {
2716 m_scenePanel->RemoveScene(static_cast<int>(i));
2717 break;
2718 }
2719 }
2720 m_blackboardScene = nullptr;
2721 }
2722
2723 // Clear tree data
2724 m_activeProject = nullptr;
2725 m_projectTrees.clear();
2726 m_projectBlackboards.clear();
2728 m_currentTreeId.clear();
2729 m_mainTreeName.clear();
2730
2731 // Force-remove all remaining scenes without unsaved-changes prompts
2732 if (m_scenePanel) {
2733 while (m_scenePanel->GetSceneCount() > 0) {
2734 m_scenePanel->RemoveScene(0, true);
2735 }
2736 m_scenePanel->ShowWelcome();
2737 }
2738
2739 SetStatusText("Project closed");
2740}
2741
2743 if (!m_activeProject) {
2744 wxMessageBox("No project is currently open.", "Information", wxOK | wxICON_INFORMATION, this);
2745 return;
2746 }
2747
2748 wxString projectPath = wxString::FromUTF8(m_activeProject->GetProjectFilePath());
2749
2750 if (projectPath.IsEmpty()) {
2751 // Need to choose a save location
2752 wxFileDialog saveDialog(this, "Save BehaviorTree Project", "",
2753 wxString::FromUTF8(m_activeProject->GetName()) + ".btproj",
2754 "BehaviorTree Project (*.btproj)|*.btproj", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
2755
2756 if (saveDialog.ShowModal() != wxID_OK) {
2757 return;
2758 }
2759
2760 projectPath = saveDialog.GetPath();
2761 }
2762
2763 if (m_activeProject->SaveToFile(projectPath.ToStdString())) {
2764 LOG_INFO("Project", "Project saved: " + projectPath.ToStdString());
2765 SetStatusText("Project saved");
2766 } else {
2767 wxMessageBox("Failed to save project.", "Error", wxOK | wxICON_ERROR, this);
2768 LOG_ERROR("Project", "Failed to save project");
2769 }
2770}
2771
2773 if (!m_activeProject) {
2774 wxMessageBox("No project is currently open.", "Information", wxOK | wxICON_INFORMATION, this);
2775 return;
2776 }
2777
2779 int result = dialog.ShowModal();
2780
2781 if (result == wxID_OK) {
2782 // Project settings may have changed
2783 SaveProject();
2784 LOG_INFO("Project", "Project settings updated");
2785 SetStatusText("Project settings saved");
2786 }
2787}
2788
2789bool MainFrame::HasActiveProject() const { return m_activeProject != nullptr; }
2790
2791std::shared_ptr<EmberCore::BehaviorTreeProject> MainFrame::GetActiveProject() const { return m_activeProject; }
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
#define LOG_ERROR(category, message)
Definition Logger.h:116
#define LOG_WARNING(category, message)
Definition Logger.h:115
#define LOG_INFO(category, message)
Definition Logger.h:114
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(MainFrame, wxFrame) EVT_MENU(wxID_EXIT
MainFrame::OnExit EVT_MENU(wxID_ABOUT, MainFrame::OnAbout) EVT_MENU(ID_NewProject
@ ID_EditorTool
Definition MainFrame.h:260
@ ID_NewProject
Definition MainFrame.h:242
@ ID_ToggleBottomPanel
Definition MainFrame.h:266
@ ID_PreviousScene
Definition MainFrame.h:251
@ ID_ToggleMaximize
Definition MainFrame.h:249
@ ID_SaveProject
Definition MainFrame.h:245
@ ID_ResetZoom
Definition MainFrame.h:252
@ ID_ParserConfig
Definition MainFrame.h:257
@ ID_MonitorTool
Definition MainFrame.h:261
@ ID_ResetUI
Definition MainFrame.h:253
@ ID_NextScene
Definition MainFrame.h:250
@ ID_OpenProject
Definition MainFrame.h:243
@ ID_Preferences
Definition MainFrame.h:256
@ ID_LogReplayTool
Definition MainFrame.h:262
@ ID_LoadXML
Definition MainFrame.h:237
@ ID_SaveAsXML
Definition MainFrame.h:239
@ ID_ToggleRightPanel
Definition MainFrame.h:267
@ ID_ToggleLeftPanel
Definition MainFrame.h:265
@ ID_CloseProject
Definition MainFrame.h:244
@ ID_SaveXML
Definition MainFrame.h:238
@ ID_ProjectSettings
Definition MainFrame.h:246
#define PERF_TIME_END(name)
#define PERF_TIME_BEGIN(name)
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)
Scene implementation for behavior tree visualization.
EmberCore::ITreeStructure * GetAbstractedTree() const
Get the abstracted tree structure.
wxPanel * GetPanel() override
Returns the wxPanel used as the scene content.
EmberCore::Node * GetBehaviorTree() const
Get the behavior tree root node (legacy interface)
EmberForge::ForgeTreeCanvas * GetTreeVisualization() const
void SetNodeSelectionCallback(NodeSelectionCallback callback)
Set callback for node selection events.
std::shared_ptr< EmberCore::ITreeStructure > GetTreeAdapter() const
void SetBehaviorTree(std::shared_ptr< EmberCore::Node > root)
Set the behavior tree for this scene using legacy Node interface.
Bottom dockable panel for logs and output.
Definition BottomPanel.h:45
static BehaviorTreeConfigManager & GetInstance()
Unified validation system for behavior trees.
ValidationResult Validate(const BehaviorTree *tree) const
Validate a behavior tree against the parser profile.
static ConfigManager & GetInstance()
Direct tree adapter that wraps BehaviorTree for unified tree operations.
std::shared_ptr< BehaviorTree > GetTree() const
Callback interface for reporting parsing progress.
Abstract interface for tree nodes that can be visualized.
Definition ITreeNode.h:31
virtual size_t GetId() const =0
virtual const String & GetName() const =0
Thread-safe XML parser using libxml2 for behavior tree files.
void SetProgressCallback(IParseProgressCallback *callback)
const std::vector< ParseError > & GetErrors() const
ProjectParseResult ParseProject(BehaviorTreeProject *project)
std::shared_ptr< BehaviorTree > ParseFromFile(const EmberCore::String &filepath)
XML serializer using libxml2 for behavior tree files.
const std::vector< SerializeError > & GetErrors() const
bool SerializeToFile(std::shared_ptr< BehaviorTree > tree, const EmberCore::String &filepath)
Serialize behavior tree to file.
Adapter class that wraps the current Node implementation to work with ITreeNode interface.
Definition NodeAdapter.h:16
Represents a node in a behavior tree structure.
Definition Node.h:20
const String & GetName() const
Definition Node.h:81
size_t GetChildCount() const
Definition Node.h:76
static ParserConfig CreateDefault()
static ProjectManager & GetInstance()
static AppPreferencesManager & GetInstance()
Application preferences configuration.
LeftPanelSettings & GetLeftPanelSettings()
bool SaveToFile(const EmberCore::String &filename) const
MainPanelSettings & GetMainPanelSettings()
ParserSettings & GetParserSettings()
WindowSettings & GetWindowSettings()
static EmberCore::String GetDefaultConfigPath()
System performance monitoring class.
static PerformanceMonitor & GetInstance()
static wxString GetDir(const wxString &relativeDir)
Get the full path to a resource directory.
void ResetView()
Resets zoom and pan to default values.
void SetSelectedNode(EmberCore::ITreeNode *node)
Sets the selected node.
Interface for scene-based UI components (e.g., views or screens).
Definition IScene.h:7
virtual wxString GetSceneType() const =0
Returns the scene type identifier.
Interface for tab-based UI components in the application.
Definition ITab.h:7
Left sidebar panel specialized for left-side functionality.
Custom loading dialog with detailed progress information.
void MarkComplete()
Mark loading as complete and change button to "Close".
void SetMaxProgress(int max)
Set the maximum value for the progress bar.
bool UpdateProgress(int current, int max, const wxString &message=wxEmptyString)
Update progress and optionally add a log message.
bool WasCancelled() const
Check if the user requested cancellation.
void AppendLog(const wxString &message, const wxString &prefix="")
Add a log message without updating progress.
Main application window for EmberForge.
Definition MainFrame.h:67
std::string m_currentTreeId
Definition MainFrame.h:222
void SyncSelection(EmberCore::ITreeNode *selectedNode)
virtual ~MainFrame()
Destructor - cleans up AUI manager and panels.
int CountTabGlobally(const wxString &tabName) const
void LogMessage(const wxString &message)
void UpdateTreeSelector()
bool m_isUpdatingFromHierarchy
Definition MainFrame.h:230
void OnSaveProject(wxCommandEvent &event)
void OnPreferences(wxCommandEvent &event)
MainPanel * m_scenePanel
Definition MainFrame.h:209
void OnToggleLeftPanel(wxCommandEvent &event)
bool IsActiveSceneBehaviorTree() const
void UpdatePropertiesTabReference(PropertiesTab *propertiesTab)
void OnPaint(wxPaintEvent &event)
void OnParserConfig(wxCommandEvent &event)
void NavigateToBlackboard(const std::string &bbId)
void OnCloseProject(wxCommandEvent &event)
std::shared_ptr< EmberCore::BehaviorTreeProject > GetActiveProject() const
void OnPreviousScene(wxCommandEvent &event)
void OnNewProject(wxCommandEvent &event)
void OpenProject()
void OnToggleMaximize(wxCommandEvent &event)
void RefreshPreferences()
BlackboardScene * m_blackboardScene
Definition MainFrame.h:221
void OpenTreeInNewScene(const std::string &treeId)
void CreateProjectOverviewScene()
void UpdateNavigatorTabReference(ForgeNavigatorTab *navigatorTab)
bool m_isUpdatingFromScene
Definition MainFrame.h:231
void OnSaveAsXML(wxCommandEvent &event)
void OnMonitorTool(wxCommandEvent &event)
void OnIdle(wxIdleEvent &event)
void RefreshHierarchyTree()
void OnToggleRightPanel(wxCommandEvent &event)
LeftSidePanel * m_leftPanel
Definition MainFrame.h:204
void CreateMenuAndStatusBar()
void OnHierarchySelectionChanged(EmberCore::ITreeNode *selectedNode)
void CenterOnNode(EmberCore::ITreeNode *node)
void ShowProjectSettings()
void OnOpenProject(wxCommandEvent &event)
RightSidePanel * m_rightPanel
Definition MainFrame.h:205
void OnFrameResize(wxSizeEvent &event)
void DoSaveXML(const wxString &filePath)
void OnPropertiesTabClosed()
void OnLogReplayTool(wxCommandEvent &event)
void OnResetZoom(wxCommandEvent &event)
void OnToggleBottomPanel(wxCommandEvent &event)
void LoadTreeIntoScene(const std::string &treeId)
void LoadXMLFile(const wxString &filePath, bool confirmOverride=false)
void CloseProject()
std::shared_ptr< EmberCore::BehaviorTreeProject > m_activeProject
Definition MainFrame.h:217
void OnNextScene(wxCommandEvent &event)
void OnAbout(wxCommandEvent &event)
wxToolBar * m_panelToggleToolbar
Definition MainFrame.h:210
void OnNavigatorTabClosed()
void OnSaveXML(wxCommandEvent &event)
BottomPanel * m_bottomPanel
Definition MainFrame.h:206
ForgeNavigatorTab * m_navigatorTab
Definition MainFrame.h:208
std::string m_mainTreeName
Definition MainFrame.h:223
void OnLoadXML(wxCommandEvent &event)
void OnSceneSelectionChanged(EmberCore::ITreeNode *selectedNode)
std::map< std::string, std::shared_ptr< EmberCore::BehaviorTree > > m_projectTrees
Definition MainFrame.h:218
void NewProject()
bool HasActiveProject() const
void OnEditorTool(wxCommandEvent &event)
wxString m_currentFilePath
Definition MainFrame.h:213
bool m_treeModified
Definition MainFrame.h:214
void SaveProject()
void OnResetUI(wxCommandEvent &event)
PropertiesTab * m_propertiesTab
Definition MainFrame.h:207
void OnProjectSettings(wxCommandEvent &event)
void InitializeConfigurationMenu()
void UpdatePanelToggleButtons()
void CreatePanelLayout()
std::map< std::string, std::vector< std::string > > m_blackboardIncludesMap
Definition MainFrame.h:220
void InvalidateCanvasLayout()
std::map< std::string, std::shared_ptr< EmberCore::Blackboard > > m_projectBlackboards
Definition MainFrame.h:219
wxSize m_lastClientSize
Definition MainFrame.h:227
void RefreshMenuHotkeys()
std::shared_ptr< EmberCore::Node > GetSharedBehaviorTree() const
void RefreshCanvasVisualization()
void LoadXMLFileInNewScene(const wxString &filePath)
void OnExit(wxCommandEvent &event)
void CreatePanelToggleToolbar()
EmberUI::ProportionalLayout m_layout
Definition MainFrame.h:226
wxAuiManager * m_auiManager
Definition MainFrame.h:203
Central panel for managing and displaying behavior tree scenes.
Definition MainPanel.h:43
Comprehensive dialog for managing parser configuration profiles.
Preferences dialog for configuring EmberForge application settings.
Dialog for opening and managing BehaviorTree projects.
wxString GetSelectedProjectPath() const
Get the selected project path (after dialog is closed with OK)
bool HasSelectedProject() const
Check if a project was selected.
Right sidebar panel specialized for right-side functionality.
std::string String
Framework-agnostic string type.
Definition String.h:14
wxSize GetScreenSize()
Returns the primary display size in pixels.
Definition DPI.h:16