Ember
Loading...
Searching...
No Matches
ParserConfigDialog.cpp
Go to the documentation of this file.
5#include "Utils/Logger.h"
7#include <cmath>
8#include <wx/filedlg.h>
9#include <wx/filename.h>
10#include <wx/gbsizer.h>
11#include <wx/listctrl.h>
12#include <wx/msgdlg.h>
13#include <wx/statbox.h>
14#include <wx/statline.h>
15#include <wx/stdpaths.h>
16#include <wx/textdlg.h>
17
18// Helper function to get resource path
19static wxString GetResourcePath(const wxString &relativePath) { return EmberForge::ResourcePath::Get(relativePath); }
20
23 EVT_BUTTON(ID_CLONE_PROFILE, ParserConfigDialog::OnCloneProfile) EVT_BUTTON(ID_DELETE_PROFILE,
24 ParserConfigDialog::OnDeleteProfile)
25 EVT_BUTTON(ID_IMPORT_PROFILE, ParserConfigDialog::OnImportProfile) EVT_BUTTON(
26 ID_EXPORT_PROFILE, ParserConfigDialog::OnExportProfile) EVT_BUTTON(ID_SET_ACTIVE,
27 ParserConfigDialog::OnSetActiveProfile)
28 EVT_BUTTON(ID_ADD_CONTROL_TYPE,
29 ParserConfigDialog::OnAddControlType) EVT_BUTTON(ID_REMOVE_CONTROL_TYPE,
30 ParserConfigDialog::OnRemoveControlType)
31 EVT_BUTTON(ID_ADD_DECORATOR_TYPE,
32 ParserConfigDialog::OnAddDecoratorType) EVT_BUTTON(ID_REMOVE_DECORATOR_TYPE,
33 ParserConfigDialog::OnRemoveDecoratorType)
34 EVT_BUTTON(ID_ADD_ACTION_TYPE,
35 ParserConfigDialog::OnAddActionType) EVT_BUTTON(ID_REMOVE_ACTION_TYPE,
36 ParserConfigDialog::OnRemoveActionType)
37 EVT_BUTTON(ID_ADD_CONDITION_TYPE, ParserConfigDialog::OnAddConditionType)
38 EVT_BUTTON(ID_REMOVE_CONDITION_TYPE, ParserConfigDialog::OnRemoveConditionType)
39 EVT_BUTTON(ID_ADD_MAPPING, ParserConfigDialog::OnAddTypeMapping)
40 EVT_BUTTON(ID_REMOVE_MAPPING, ParserConfigDialog::OnRemoveTypeMapping)
41 EVT_CHECKBOX(ID_TOGGLE_CUSTOM_VALIDATION,
42 ParserConfigDialog::OnToggleCustomValidation)
43 EVT_BUTTON(ID_TEST_VALIDATE, ParserConfigDialog::OnTestValidate)
44 EVT_BUTTON(ID_PREVIEW_JSON, ParserConfigDialog::OnPreviewJSON)
45 EVT_BUTTON(ID_RESET_DEFAULTS, ParserConfigDialog::OnResetDefaults)
46 EVT_BUTTON(ID_SAVE, ParserConfigDialog::OnSave)
47 EVT_BUTTON(ID_SAVE_AS, ParserConfigDialog::OnSaveAs)
48 EVT_BUTTON(wxID_CANCEL, ParserConfigDialog::OnCancel)
49 EVT_TIMER(ID_PULSE_TIMER,
50 ParserConfigDialog::OnPulseTimer)
52
54 wxWindow *parent)
55 : EmberUI::ScalableDialog(parent, wxID_ANY, "Parser Configuration", wxSize(1400, 1000)), m_currentProfile(nullptr),
56 m_modified(false), m_pulseTimer(nullptr), m_pulsePhase(0.0f), m_normalBg(50, 50, 50) // Dark background
57 ,
58 m_highlightBg(255, 250, 150) // Soft yellow highlight
59{
60 CreateLayout();
61 LoadProfiles();
62 Centre();
63
64 // Start the pulse animation timer (30ms = ~33 fps for smooth animation)
65 m_pulseTimer = new wxTimer(this, ID_PULSE_TIMER);
66 m_pulseTimer->Start(30);
67}
68
70 wxBoxSizer *mainSizer = new wxBoxSizer(wxHORIZONTAL);
71
72 // Left sidebar (200px fixed width)
73 wxPanel *leftPanel = CreateLeftSidebar(this);
74 mainSizer->Add(leftPanel, 0, wxEXPAND | wxALL, 5);
75
76 // Right panel (expandable)
77 wxPanel *rightPanel = CreateRightPanel(this);
78 mainSizer->Add(rightPanel, 1, wxEXPAND | wxALL, 5);
79
80 SetSizer(mainSizer);
81}
82
83wxPanel *ParserConfigDialog::CreateLeftSidebar(wxWindow *parent) {
84 wxPanel *panel = new wxPanel(parent, wxID_ANY);
85 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
86
87 // Profile list
88 wxStaticText *label = new wxStaticText(panel, wxID_ANY, "Profiles:");
89 sizer->Add(label, 0, wxALL, 5);
90
91 // Use wxListCtrl for per-item color support
92 m_profileList = new wxListCtrl(panel, ID_PROFILE_LIST, wxDefaultPosition, wxDefaultSize,
93 wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER | wxBORDER_SUNKEN);
94 m_profileList->SetMinSize(wxSize(200, -1));
95
96 // Set larger, bold font for profile list
97 wxFont profileFont = m_profileList->GetFont();
98 profileFont.SetPointSize(profileFont.GetPointSize() + 2); // Larger text
99 profileFont.MakeBold(); // Bold text
100 m_profileList->SetFont(profileFont);
101
102 // Add single column (header is hidden)
103 m_profileList->InsertColumn(0, "Profile", wxLIST_FORMAT_LEFT, 200);
104
105 sizer->Add(m_profileList, 1, wxEXPAND | wxALL, 5);
106
107 // Buttons
108 m_newProfileBtn = new wxButton(panel, ID_NEW_PROFILE, "New Profile");
109 sizer->Add(m_newProfileBtn, 0, wxEXPAND | wxALL, 2);
110
111 m_cloneProfileBtn = new wxButton(panel, ID_CLONE_PROFILE, "Clone Profile");
112 sizer->Add(m_cloneProfileBtn, 0, wxEXPAND | wxALL, 2);
113
114 m_deleteProfileBtn = new wxButton(panel, ID_DELETE_PROFILE, "Delete Profile");
115 sizer->Add(m_deleteProfileBtn, 0, wxEXPAND | wxALL, 2);
116
117 sizer->AddSpacer(10);
118
119 m_importProfileBtn = new wxButton(panel, ID_IMPORT_PROFILE, "Import Profile...");
120 sizer->Add(m_importProfileBtn, 0, wxEXPAND | wxALL, 2);
121
122 m_exportProfileBtn = new wxButton(panel, ID_EXPORT_PROFILE, "Export Profile...");
123 sizer->Add(m_exportProfileBtn, 0, wxEXPAND | wxALL, 2);
124
125 sizer->AddSpacer(10);
126
127 m_setActiveBtn = new wxButton(panel, ID_SET_ACTIVE, "Set as Active");
128 sizer->Add(m_setActiveBtn, 0, wxEXPAND | wxALL, 2);
129
130 panel->SetSizer(sizer);
131 return panel;
132}
133
134wxPanel *ParserConfigDialog::CreateRightPanel(wxWindow *parent) {
135 wxPanel *panel = new wxPanel(parent, wxID_ANY);
136 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
137
138 // Notebook with tabs
139 m_notebook = new wxNotebook(panel, wxID_ANY);
140
141 m_notebook->AddPage(CreateDocumentStructureTab(m_notebook), "Document Structure");
142 m_notebook->AddPage(CreateTreeElementsTab(m_notebook), "Tree Elements");
143 m_notebook->AddPage(CreateNodeElementsTab(m_notebook), "Node Elements");
144 m_notebook->AddPage(CreateNodeClassificationTab(m_notebook), "Node Classification");
145 m_notebook->AddPage(CreateBlackboardTab(m_notebook), "Blackboard");
146 m_notebook->AddPage(CreateAdvancedTab(m_notebook), "Advanced");
147
148 sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5);
149
150 // Bottom panel
151 wxPanel *bottomPanel = CreateBottomPanel(panel);
152 sizer->Add(bottomPanel, 0, wxEXPAND | wxALL, 5);
153
154 panel->SetSizer(sizer);
155 return panel;
156}
157
159 wxPanel *panel = new wxPanel(notebook);
160 wxGridBagSizer *sizer = new wxGridBagSizer(10, 10); // Increased from 5,5 to 10,10 for more spacing
161
162 int row = 0;
163
164 // Root element name
165 wxBoxSizer *labelSizer1 = new wxBoxSizer(wxHORIZONTAL);
166
167 // Create info button with all state images - positioned first (left)
168 // Get button size dynamically from image with safety check
169 wxBitmap infoNormal(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
170 wxBitmap infoHover(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
171 wxBitmap infoPressed(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
172 wxBitmap infoDisabled(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
173
174 wxBitmapButton *infoBtn1 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
175 wxBORDER_NONE | wxBU_EXACTFIT);
176 infoBtn1->SetBitmapHover(infoHover);
177 infoBtn1->SetBitmapPressed(infoPressed);
178 infoBtn1->SetBitmapDisabled(infoDisabled);
179 infoBtn1->SetToolTip("Click for detailed information");
180 infoBtn1->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
181 wxString helpText = "ROOT ELEMENT NAME\n"
182 "===========================================================\n\n"
183 "WHAT IS IT?\n"
184 "The name of the top-level XML element that wraps all behavior trees.\n"
185 "This is the outermost container in your XML file.\n\n"
186 "COMMON VALUES:\n"
187 " - 'root' - BehaviorTree.CPP standard format\n"
188 " - 'BehaviorTrees' - Some Unity implementations\n"
189 " - 'TreeCollection' - Custom formats\n\n"
190 "IMPORTANT:\n"
191 "This element MUST be present in your XML file as the outermost container.\n"
192 "All behavior tree definitions must be nested inside this root element.\n\n"
193 "EXAMPLE XML STRUCTURE:\n"
194 " <root main_tree_to_execute=\"MainTree\">\n"
195 " <BehaviorTree ID=\"MainTree\">\n"
196 " <Sequence>\n"
197 " <Action ID=\"MoveForward\"/>\n"
198 " </Sequence>\n"
199 " </BehaviorTree>\n"
200 " </root>\n\n"
201 "If your XML uses a different name (e.g., <Trees>), enter that name here.";
202 HelpDialog dlg(this, "Root Element Name - Help", helpText);
203 dlg.ShowModal();
204 });
205 labelSizer1->Add(infoBtn1, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
206 labelSizer1->Add(new wxStaticText(panel, wxID_ANY, "Root Element Name:"), 0, wxALIGN_CENTER_VERTICAL);
207 sizer->Add(labelSizer1, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
208
209 m_rootElement = new wxTextCtrl(panel, wxID_ANY);
210 m_rootElement->SetToolTip("Top-level XML element name (e.g., 'root')");
211 m_rootElement->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
212 sizer->Add(m_rootElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
213 row++;
214
215 // Main tree attribute
216 wxBoxSizer *labelSizer2 = new wxBoxSizer(wxHORIZONTAL);
217
218 wxBitmapButton *infoBtn2 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
219 wxBORDER_NONE | wxBU_EXACTFIT);
220 infoBtn2->SetBitmapHover(infoHover);
221 infoBtn2->SetBitmapPressed(infoPressed);
222 infoBtn2->SetBitmapDisabled(infoDisabled);
223 infoBtn2->SetToolTip("Click for detailed information");
224 infoBtn2->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
225 wxString helpText = "MAIN TREE ATTRIBUTE\n"
226 "===========================================================\n\n"
227 "WHAT IS IT?\n"
228 "The attribute on the root element that specifies which behavior tree\n"
229 "should be executed first when your XML file contains multiple trees.\n\n"
230 "COMMON VALUES:\n"
231 " - 'main_tree_to_execute' - BehaviorTree.CPP standard\n"
232 " - 'entry_point' - Alternative naming convention\n"
233 " - 'start_tree' - Some custom implementations\n\n"
234 "WHY DO YOU NEED IT?\n"
235 "When your XML file contains multiple behavior tree definitions (main tree,\n"
236 "subtrees, utility trees), this attribute tells the system which one is the\n"
237 "primary entry point.\n\n"
238 "EXAMPLE:\n"
239 " <root main_tree_to_execute=\"PlayerAI\">\n"
240 " <BehaviorTree ID=\"PlayerAI\">\n"
241 " <!-- This tree will be loaded first -->\n"
242 " </BehaviorTree>\n"
243 " <BehaviorTree ID=\"EnemyAI\">\n"
244 " <!-- This tree is available as a subtree -->\n"
245 " </BehaviorTree>\n"
246 " </root>\n\n"
247 "FALLBACK BEHAVIOR:\n"
248 "If this attribute is missing or empty, the parser will automatically\n"
249 "load the first behavior tree it finds in the file.";
250 HelpDialog dlg(this, "Main Tree Attribute - Help", helpText);
251 dlg.ShowModal();
252 });
253 labelSizer2->Add(infoBtn2, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
254 labelSizer2->Add(new wxStaticText(panel, wxID_ANY, "Main Tree Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
255 sizer->Add(labelSizer2, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
256
257 m_mainTreeAttr = new wxTextCtrl(panel, wxID_ANY);
258 m_mainTreeAttr->SetToolTip("Attribute specifying entry point tree (e.g., 'main_tree_to_execute')");
259 m_mainTreeAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
260 sizer->Add(m_mainTreeAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
261 row++;
262
263 // Case sensitive
264 wxBoxSizer *caseSizer = new wxBoxSizer(wxHORIZONTAL);
265
266 wxBitmapButton *infoBtn3 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
267 wxBORDER_NONE | wxBU_EXACTFIT);
268 infoBtn3->SetBitmapHover(infoHover);
269 infoBtn3->SetBitmapPressed(infoPressed);
270 infoBtn3->SetBitmapDisabled(infoDisabled);
271 infoBtn3->SetToolTip("Click for detailed information");
272 infoBtn3->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
273 wxString helpText = "CASE SENSITIVE ELEMENT NAMES\n"
274 "===========================================================\n\n"
275 "Controls whether element and attribute names must match the exact\n"
276 "case specified in your configuration.\n\n"
277 "ENABLED (Recommended):\n"
278 " - <BehaviorTree> and <behaviortree> are treated as DIFFERENT\n"
279 " - More strict parsing - catches typos and inconsistencies\n"
280 " - Standard for most XML parsers and behavior tree formats\n"
281 " - Best for well-defined, consistent XML formats\n\n"
282 "DISABLED:\n"
283 " - <BehaviorTree>, <behaviortree>, <BEHAVIORTREE> all treated as SAME\n"
284 " - More forgiving - useful for legacy/inconsistent XML files\n"
285 " - May hide formatting issues in your XML\n"
286 " - Use ONLY if your XML has inconsistent casing\n\n"
287 "EXAMPLE:\n"
288 "If ENABLED and you configure 'BehaviorTree' as the tree element:\n"
289 " [YES] <BehaviorTree ID=\"Test\"> - ACCEPTED\n"
290 " [NO] <behaviortree ID=\"Test\"> - REJECTED (case mismatch)\n\n"
291 "If DISABLED:\n"
292 " [YES] <BehaviorTree ID=\"Test\"> - ACCEPTED\n"
293 " [YES] <behaviortree ID=\"Test\"> - ACCEPTED\n"
294 " [YES] <BEHAVIORTREE ID=\"Test\"> - ACCEPTED\n\n"
295 "RECOMMENDATION:\n"
296 "Keep this ENABLED unless working with legacy files that have\n"
297 "inconsistent casing. Strict validation helps catch errors early!";
298 HelpDialog dlg(this, "Case Sensitive - Help", helpText);
299 dlg.ShowModal();
300 });
301 caseSizer->Add(infoBtn3, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
302 m_caseSensitive = new wxCheckBox(panel, wxID_ANY, "Case Sensitive Element Names");
303 m_caseSensitive->SetToolTip("Element/attribute names must match exact case");
304 caseSizer->Add(m_caseSensitive, 0, wxALIGN_CENTER_VERTICAL);
305 sizer->Add(caseSizer, wxGBPosition(row, 0), wxGBSpan(1, 2));
306 row++;
307
308 // Allow unknown elements
309 wxBoxSizer *unknownSizer = new wxBoxSizer(wxHORIZONTAL);
310
311 wxBitmapButton *infoBtn4 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
312 wxBORDER_NONE | wxBU_EXACTFIT);
313 infoBtn4->SetBitmapHover(infoHover);
314 infoBtn4->SetBitmapPressed(infoPressed);
315 infoBtn4->SetBitmapDisabled(infoDisabled);
316 infoBtn4->SetToolTip("Click for detailed information");
317 infoBtn4->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
318 wxString helpText = "ALLOW UNKNOWN ELEMENTS\n"
319 "===========================================================\n\n"
320 "Controls how the parser handles XML elements that are NOT defined\n"
321 "in your configuration.\n\n"
322 "ENABLED:\n"
323 " - Unknown elements are SILENTLY IGNORED\n"
324 " - Parsing continues without warnings\n"
325 " - Useful for XML with documentation, metadata, or extensions\n"
326 " - More flexible for varying XML formats\n\n"
327 "DISABLED (Recommended):\n"
328 " - Unknown elements cause WARNING messages in the log\n"
329 " - Helps catch typos (e.g., 'Behaviortree' vs 'BehaviorTree')\n"
330 " - Stricter validation ensures XML matches expectations\n"
331 " - Better for catching configuration errors\n\n"
332 "EXAMPLE - XML with unknown elements:\n"
333 " <root>\n"
334 " <BehaviorTree ID=\"Test\">...</BehaviorTree>\n"
335 " <Documentation>Comment</Documentation> <- Unknown\n"
336 " <Metadata author=\"John\"/> <- Unknown\n"
337 " </root>\n\n"
338 "If ENABLED: Parser ignores <Documentation> and <Metadata>\n"
339 "If DISABLED: Parser logs warnings about unexpected elements\n\n"
340 "RECOMMENDATION:\n"
341 "Keep DISABLED during development to catch errors.\n"
342 "Enable only if you need to parse XML with extra metadata.";
343 HelpDialog dlg(this, "Allow Unknown Elements - Help", helpText);
344 dlg.ShowModal();
345 });
346 unknownSizer->Add(infoBtn4, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
347 m_allowUnknownElements = new wxCheckBox(panel, wxID_ANY, "Allow Unknown Elements");
348 m_allowUnknownElements->SetToolTip("Silently ignore unrecognized XML elements");
349 unknownSizer->Add(m_allowUnknownElements, 0, wxALIGN_CENTER_VERTICAL);
350 sizer->Add(unknownSizer, wxGBPosition(row, 0), wxGBSpan(1, 2));
351 row++;
352
353 // Whitespace handling
354 wxBoxSizer *labelSizer5 = new wxBoxSizer(wxHORIZONTAL);
355
356 wxBitmapButton *infoBtn5 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
357 wxBORDER_NONE | wxBU_EXACTFIT);
358 infoBtn5->SetBitmapHover(infoHover);
359 infoBtn5->SetBitmapPressed(infoPressed);
360 infoBtn5->SetBitmapDisabled(infoDisabled);
361 infoBtn5->SetToolTip("Click for detailed information");
362 infoBtn5->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
363 wxString helpText = "WHITESPACE HANDLING\n"
364 "===========================================================\n\n"
365 "Controls how whitespace (spaces, tabs, newlines) in attribute values\n"
366 "and text content is processed during parsing.\n\n"
367 "OPTIONS:\n\n"
368 "PRESERVE:\n"
369 " - Keeps ALL whitespace exactly as it appears\n"
370 " - ' Hello World ' stays as ' Hello World '\n"
371 " - Use if whitespace is meaningful in your data\n"
372 " - Rarely needed for behavior trees\n\n"
373 "TRIM (Recommended):\n"
374 " - Removes leading and trailing whitespace ONLY\n"
375 " - ' Hello World ' becomes 'Hello World'\n"
376 " - Internal spaces preserved: 'Hello World' stays 'Hello World'\n"
377 " - Best for most behavior tree formats\n"
378 " - Handles formatting inconsistencies gracefully\n\n"
379 "NORMALIZE:\n"
380 " - Trims AND collapses multiple spaces to single space\n"
381 " - ' Hello World ' becomes 'Hello World'\n"
382 " - Useful for cleaning poorly formatted XML\n"
383 " - May affect intentional multi-spacing\n\n"
384 "EXAMPLES:\n\n"
385 "Value: name=' Patrol Enemy '\n"
386 " - PRESERVE: ' Patrol Enemy '\n"
387 " - TRIM: 'Patrol Enemy'\n"
388 " - NORMALIZE: 'Patrol Enemy'\n\n"
389 "Value: name='Move To Target'\n"
390 " - PRESERVE: 'Move To Target'\n"
391 " - TRIM: 'Move To Target'\n"
392 " - NORMALIZE: 'Move To Target'\n\n"
393 "RECOMMENDATION:\n"
394 "Use TRIM for clean, consistent parsing that handles\n"
395 "formatting variations without losing meaningful content.";
396 HelpDialog dlg(this, "Whitespace Handling - Help", helpText);
397 dlg.ShowModal();
398 });
399 labelSizer5->Add(infoBtn5, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
400 labelSizer5->Add(new wxStaticText(panel, wxID_ANY, "Whitespace Handling:"), 0, wxALIGN_CENTER_VERTICAL);
401 sizer->Add(labelSizer5, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
402
403 wxArrayString whitespaceChoices;
404 whitespaceChoices.Add("Preserve");
405 whitespaceChoices.Add("Trim");
406 whitespaceChoices.Add("Normalize");
407 m_whitespaceHandling = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, whitespaceChoices);
408 m_whitespaceHandling->SetSelection(1); // Trim by default
409 m_whitespaceHandling->SetToolTip("How to process spaces/tabs/newlines (Trim recommended)");
410 sizer->Add(m_whitespaceHandling, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
411 row++;
412
413 // Naming Convention
414 wxBoxSizer *namingSizer = new wxBoxSizer(wxHORIZONTAL);
415
416 wxBitmapButton *infoBtn6 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
417 wxBORDER_NONE | wxBU_EXACTFIT);
418 infoBtn6->SetBitmapHover(infoHover);
419 infoBtn6->SetBitmapPressed(infoPressed);
420 infoBtn6->SetBitmapDisabled(infoDisabled);
421 infoBtn6->SetToolTip("Click for detailed information");
422 infoBtn6->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
423 wxString helpText = "NAMING CONVENTION\n"
424 "===========================================================\n\n"
425 "Controls how strictly element and attribute naming rules are enforced.\n"
426 "When you switch conventions, all fields are immediately re-validated\n"
427 "and invalid names are highlighted with a pulsing yellow background.\n\n"
428 "===========================================================\n\n"
429 "STRICT MODE:\n"
430 " Allowed: Letters (a-z, A-Z) and underscores (_) ONLY\n"
431 " Not allowed: Numbers, hyphens, dots, or any special characters\n"
432 " Must start with: Letter or underscore\n"
433 " \n"
434 " Use for: Maximum safety and consistency\n"
435 " Best for: Production environments, strict standards\n\n"
436 "===========================================================\n\n"
437 "BALANCED MODE (Recommended):\n"
438 " Allowed: Letters, underscores, digits (0-9), and hyphens (-)\n"
439 " Not allowed: Dots, spaces, or other special characters\n"
440 " Must start with: Letter or underscore (NOT a number)\n"
441 " \n"
442 " Use for: Most projects - flexible yet safe\n"
443 " Best for: Standard behavior tree projects\n\n"
444 "===========================================================\n\n"
445 "LOOSE MODE:\n"
446 " Allowed: Most characters that don't break XML\n"
447 " Includes: Letters, digits, underscores, hyphens, dots, colons\n"
448 " Not allowed: <, >, &, quotes, spaces, brackets, and other\n"
449 " XML-breaking characters\n"
450 " Must start with: Letter, underscore, or colon\n"
451 " \n"
452 " Use for: Legacy XML files, maximum compatibility\n"
453 " Best for: Working with existing/legacy systems\n\n"
454 "===========================================================\n\n"
455 "EXAMPLES:\n\n"
456 "Name: 'MyAction' All modes accept\n"
457 "Name: 'My_Action' All modes accept\n"
458 "Name: 'My-Action' STRICT rejects | BALANCED/LOOSE accept\n"
459 "Name: 'MyAction123' STRICT rejects | BALANCED/LOOSE accept\n"
460 "Name: 'My.Action' STRICT/BALANCED reject | LOOSE accepts\n"
461 "Name: 'My:Action' STRICT/BALANCED reject | LOOSE accepts\n"
462 "Name: '123Action' STRICT/BALANCED reject | LOOSE rejects\n"
463 "Name: 'My Action' All modes reject (contains space)\n"
464 "Name: 'My<Action>' All modes reject (breaks XML)\n\n"
465 "===========================================================\n\n"
466 "LIVE VALIDATION:\n"
467 " - Switch between conventions to see which fields become invalid\n"
468 " - Invalid fields pulse with yellow highlighting\n"
469 " - Fix invalid names before saving your configuration\n\n"
470 "RECOMMENDATION:\n"
471 "Start with BALANCED - it provides excellent validation while\n"
472 "supporting common naming patterns like 'Action-Name' and 'Node123'.\n"
473 "Use STRICT for maximum safety or LOOSE for legacy compatibility.";
474 HelpDialog dlg(this, "Naming Convention - Help", helpText);
475 dlg.ShowModal();
476 });
477 namingSizer->Add(infoBtn6, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
478 namingSizer->Add(new wxStaticText(panel, wxID_ANY, "Naming Convention:"), 0, wxALIGN_CENTER_VERTICAL);
479 sizer->Add(namingSizer, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
480
481 // Create a horizontal box for the three checkboxes with labels below
482 wxBoxSizer *checkboxesSizer = new wxBoxSizer(wxHORIZONTAL);
483
484 // Strict checkbox with label
485 wxBoxSizer *strictSizer = new wxBoxSizer(wxVERTICAL);
486 m_namingConventionStrict = new wxCheckBox(panel, wxID_ANY, "");
487 m_namingConventionStrict->SetToolTip("Strict naming validation");
488 strictSizer->Add(m_namingConventionStrict, 0, wxALIGN_CENTER_HORIZONTAL);
489 strictSizer->Add(new wxStaticText(panel, wxID_ANY, "Strict"), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, 2);
490 checkboxesSizer->Add(strictSizer, 0, wxRIGHT, 15);
491
492 // Balanced checkbox with label
493 wxBoxSizer *balancedSizer = new wxBoxSizer(wxVERTICAL);
494 m_namingConventionBalanced = new wxCheckBox(panel, wxID_ANY, "");
495 m_namingConventionBalanced->SetToolTip("Balanced naming validation (recommended)");
496 balancedSizer->Add(m_namingConventionBalanced, 0, wxALIGN_CENTER_HORIZONTAL);
497 balancedSizer->Add(new wxStaticText(panel, wxID_ANY, "Balanced"), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, 2);
498 checkboxesSizer->Add(balancedSizer, 0, wxRIGHT, 15);
499
500 // Loose checkbox with label
501 wxBoxSizer *looseSizer = new wxBoxSizer(wxVERTICAL);
502 m_namingConventionLoose = new wxCheckBox(panel, wxID_ANY, "");
503 m_namingConventionLoose->SetToolTip("Loose naming validation");
504 looseSizer->Add(m_namingConventionLoose, 0, wxALIGN_CENTER_HORIZONTAL);
505 looseSizer->Add(new wxStaticText(panel, wxID_ANY, "Loose"), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, 2);
506 checkboxesSizer->Add(looseSizer, 0);
507
508 sizer->Add(checkboxesSizer, wxGBPosition(row, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
509 row++;
510
511 // Bind checkbox events to ensure only one is selected at a time
512 // and trigger validation when convention changes
513 m_namingConventionStrict->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) {
514 if (event.IsChecked()) {
515 m_namingConventionBalanced->SetValue(false);
516 m_namingConventionLoose->SetValue(false);
517 }
519 });
520 m_namingConventionBalanced->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) {
521 if (event.IsChecked()) {
522 m_namingConventionStrict->SetValue(false);
523 m_namingConventionLoose->SetValue(false);
524 }
526 });
527 m_namingConventionLoose->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) {
528 if (event.IsChecked()) {
529 m_namingConventionStrict->SetValue(false);
530 m_namingConventionBalanced->SetValue(false);
531 }
533 });
534
535 sizer->AddGrowableCol(1);
536 panel->SetSizer(sizer);
537 return panel;
538}
539
540wxPanel *ParserConfigDialog::CreateTreeElementsTab(wxNotebook *notebook) {
541 wxPanel *panel = new wxPanel(notebook);
542 wxGridBagSizer *sizer = new wxGridBagSizer(5, 5);
543
544 // Load info button bitmaps
545 wxBitmap infoNormal(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
546 wxBitmap infoHover(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
547 wxBitmap infoPressed(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
548 wxBitmap infoDisabled(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
549
550 int row = 0;
551
552 // Behavior tree element
553 wxBoxSizer *labelSizer1 = new wxBoxSizer(wxHORIZONTAL);
554 wxBitmapButton *infoBtn1 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
555 wxBORDER_NONE | wxBU_EXACTFIT);
556 infoBtn1->SetBitmapHover(infoHover);
557 infoBtn1->SetBitmapPressed(infoPressed);
558 infoBtn1->SetBitmapDisabled(infoDisabled);
559 infoBtn1->SetToolTip("Click for detailed information");
560 infoBtn1->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
561 wxString helpText = "BEHAVIOR TREE ELEMENT\n"
562 "===========================================================\n\n"
563 "WHAT IS IT?\n"
564 "The XML element name that represents a behavior tree definition\n"
565 "in your XML file. This is the main container for each tree.\n\n"
566 "COMMON VALUES:\n"
567 " - 'BehaviorTree' - Standard BehaviorTree.CPP format\n"
568 " - 'Tree' - Simplified/custom format\n"
569 " - 'btree' - Alternative naming\n\n"
570 "HOW IT WORKS:\n"
571 "The parser scans your XML looking for elements with this exact name.\n"
572 "Each matching element is treated as a separate behavior tree definition.\n\n"
573 "EXAMPLE:\n"
574 " <root>\n"
575 " <BehaviorTree ID=\"MainTree\">\n"
576 " <Sequence>\n"
577 " <Action name=\"DoSomething\"/>\n"
578 " </Sequence>\n"
579 " </BehaviorTree>\n"
580 " </root>\n\n"
581 "IMPORTANT:\n"
582 " - Name must match EXACTLY (case-sensitive if enabled)\n"
583 " - All your behavior trees must use this element name\n"
584 " - Typical values: 'BehaviorTree', 'Tree', or custom names";
585 HelpDialog dlg(this, "Behavior Tree Element - Help", helpText);
586 dlg.ShowModal();
587 });
588 labelSizer1->Add(infoBtn1, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
589 labelSizer1->Add(new wxStaticText(panel, wxID_ANY, "Behavior Tree Element:"), 0, wxALIGN_CENTER_VERTICAL);
590 sizer->Add(labelSizer1, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
591 m_behaviorTreeElement = new wxTextCtrl(panel, wxID_ANY);
592 m_behaviorTreeElement->SetToolTip("Element name for behavior trees (e.g., 'BehaviorTree')");
594 sizer->Add(m_behaviorTreeElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
595 row++;
596
597 // Tree ID attribute
598 wxBoxSizer *labelSizer2 = new wxBoxSizer(wxHORIZONTAL);
599 wxBitmapButton *infoBtn2 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
600 wxBORDER_NONE | wxBU_EXACTFIT);
601 infoBtn2->SetBitmapHover(infoHover);
602 infoBtn2->SetBitmapPressed(infoPressed);
603 infoBtn2->SetBitmapDisabled(infoDisabled);
604 infoBtn2->SetToolTip("Click for detailed information");
605 infoBtn2->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
606 wxString helpText = "TREE ID ATTRIBUTE\n"
607 "===========================================================\n\n"
608 "WHAT IS IT?\n"
609 "The XML attribute name that contains the unique identifier for\n"
610 "each behavior tree in your file.\n\n"
611 "COMMON VALUES:\n"
612 " - 'ID' - Standard BehaviorTree.CPP format (most common)\n"
613 " - 'name' - Alternative naming convention\n"
614 " - 'id' - Lowercase variant\n"
615 " - 'tree_id' - Explicit naming\n\n"
616 "WHY DO YOU NEED IT?\n"
617 "The ID serves multiple critical purposes:\n"
618 " - Uniquely identifies each behavior tree\n"
619 " - Enables subtree references (one tree calling another)\n"
620 " - Supports multiple trees in a single XML file\n"
621 " - Used by the main tree attribute to specify entry point\n\n"
622 "EXAMPLE:\n"
623 " <BehaviorTree ID=\"MainTree\">\n"
624 " <SubTree ID=\"PatrolBehavior\"/>\n"
625 " </BehaviorTree>\n"
626 " <BehaviorTree ID=\"PatrolBehavior\">\n"
627 " <!-- This tree can be called by MainTree -->\n"
628 " </BehaviorTree>\n\n"
629 "IMPORTANT:\n"
630 " - Each tree MUST have a unique ID\n"
631 " - IDs are used for subtree references";
632 HelpDialog dlg(this, "Tree ID Attribute - Help", helpText);
633 dlg.ShowModal();
634 });
635 labelSizer2->Add(infoBtn2, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
636 labelSizer2->Add(new wxStaticText(panel, wxID_ANY, "Tree ID Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
637 sizer->Add(labelSizer2, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
638 m_treeIdAttr = new wxTextCtrl(panel, wxID_ANY);
639 m_treeIdAttr->SetToolTip("Attribute name for tree identifier (e.g., 'ID')");
640 m_treeIdAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
641 sizer->Add(m_treeIdAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
642 row++;
643
644 // Allow multiple trees
645 wxBoxSizer *labelSizer3 = new wxBoxSizer(wxHORIZONTAL);
646 wxBitmapButton *infoBtn3 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
647 wxBORDER_NONE | wxBU_EXACTFIT);
648 infoBtn3->SetBitmapHover(infoHover);
649 infoBtn3->SetBitmapPressed(infoPressed);
650 infoBtn3->SetBitmapDisabled(infoDisabled);
651 infoBtn3->SetToolTip("Click for detailed information");
652 infoBtn3->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
653 wxString helpText = "ALLOW MULTIPLE TREES IN FILE\n"
654 "===========================================================\n\n"
655 "Controls whether your XML file can contain multiple behavior tree\n"
656 "definitions or just one.\n\n"
657 "ENABLED (Recommended):\n"
658 " - Multiple <BehaviorTree> elements can exist in one file\n"
659 " - Each tree MUST have a unique ID\n"
660 " - Perfect for organizing related trees together\n"
661 " - Enables subtree references within the same file\n"
662 " - Common in BehaviorTree.CPP and complex AI systems\n\n"
663 "DISABLED:\n"
664 " - Only ONE <BehaviorTree> element allowed per file\n"
665 " - Simpler structure for basic projects\n"
666 " - Parser will REJECT files with multiple trees\n"
667 " - Forces one-tree-per-file organization\n\n"
668 "EXAMPLE (ENABLED):\n"
669 " <root>\n"
670 " <BehaviorTree ID=\"MainAI\">\n"
671 " <SubTree ID=\"Patrol\"/>\n"
672 " </BehaviorTree>\n"
673 " <BehaviorTree ID=\"Patrol\">\n"
674 " <!-- Subtree definition -->\n"
675 " </BehaviorTree>\n"
676 " </root>\n\n"
677 "RECOMMENDATION:\n"
678 "Enable this for flexibility and better tree organization.";
679 HelpDialog dlg(this, "Allow Multiple Trees - Help", helpText);
680 dlg.ShowModal();
681 });
682 labelSizer3->Add(infoBtn3, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
683 m_allowMultipleTrees = new wxCheckBox(panel, wxID_ANY, "Allow Multiple Trees in File");
684 m_allowMultipleTrees->SetToolTip("Whether XML can contain multiple behavior tree definitions");
685 labelSizer3->Add(m_allowMultipleTrees, 0, wxALIGN_CENTER_VERTICAL);
686 sizer->Add(labelSizer3, wxGBPosition(row, 0), wxGBSpan(1, 2));
687 row++;
688
689 // Subtree element
690 wxBoxSizer *labelSizer4 = new wxBoxSizer(wxHORIZONTAL);
691 wxBitmapButton *infoBtn4 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
692 wxBORDER_NONE | wxBU_EXACTFIT);
693 infoBtn4->SetBitmapHover(infoHover);
694 infoBtn4->SetBitmapPressed(infoPressed);
695 infoBtn4->SetBitmapDisabled(infoDisabled);
696 infoBtn4->SetToolTip("Click for detailed information");
697 infoBtn4->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
698 wxString helpText = "SUBTREE ELEMENT\n"
699 "===========================================================\n\n"
700 "WHAT IS IT?\n"
701 "The XML element name that represents a subtree reference - a special\n"
702 "node that calls another behavior tree by its ID.\n\n"
703 "COMMON VALUES:\n"
704 " - 'SubTree' - Standard BehaviorTree.CPP format (recommended)\n"
705 " - 'CallTree' - Alternative naming\n"
706 " - 'TreeReference' - Explicit variant\n"
707 " - 'Include' - Some custom implementations\n\n"
708 "HOW IT WORKS:\n"
709 "When the execution reaches a SubTree node, it:\n"
710 " 1. Pauses the current tree\n"
711 " 2. Executes the referenced tree completely\n"
712 " 3. Returns the referenced tree's result (SUCCESS/FAILURE)\n"
713 " 4. Continues with the current tree\n\n"
714 "EXAMPLE:\n"
715 " <BehaviorTree ID=\"MainAI\">\n"
716 " <Sequence>\n"
717 " <SubTree ID=\"PatrolBehavior\"/>\n"
718 " <Action name=\"Attack\"/>\n"
719 " </Sequence>\n"
720 " </BehaviorTree>\n\n"
721 "BENEFITS:\n"
722 " - Reuse trees in multiple places\n"
723 " - Create modular, maintainable structures\n"
724 " - Break complex behaviors into components";
725 HelpDialog dlg(this, "SubTree Element - Help", helpText);
726 dlg.ShowModal();
727 });
728 labelSizer4->Add(infoBtn4, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
729 labelSizer4->Add(new wxStaticText(panel, wxID_ANY, "SubTree Element:"), 0, wxALIGN_CENTER_VERTICAL);
730 sizer->Add(labelSizer4, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
731 m_subtreeElement = new wxTextCtrl(panel, wxID_ANY);
732 m_subtreeElement->SetToolTip("Element name for subtree references (e.g., 'SubTree')");
734 sizer->Add(m_subtreeElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
735 row++;
736
737 // Subtree reference attribute
738 wxBoxSizer *labelSizer5 = new wxBoxSizer(wxHORIZONTAL);
739 wxBitmapButton *infoBtn5 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
740 wxBORDER_NONE | wxBU_EXACTFIT);
741 infoBtn5->SetBitmapHover(infoHover);
742 infoBtn5->SetBitmapPressed(infoPressed);
743 infoBtn5->SetBitmapDisabled(infoDisabled);
744 infoBtn5->SetToolTip("Click for detailed information");
745 infoBtn5->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
746 wxString helpText = "SUBTREE REFERENCE ATTRIBUTE\n"
747 "===========================================================\n\n"
748 "WHAT IS IT?\n"
749 "The XML attribute name on a SubTree node that contains the ID of\n"
750 "the behavior tree being referenced/called.\n\n"
751 "COMMON VALUES:\n"
752 " - 'ID' - Standard BehaviorTree.CPP format (most common)\n"
753 " - 'treeID' - Explicit variant\n"
754 " - 'ref' - Short form\n"
755 " - 'tree' - Simple naming\n\n"
756 "HOW IT WORKS:\n"
757 "When the parser encounters a SubTree node:\n"
758 " 1. Reads this attribute to get the referenced tree ID\n"
759 " 2. Searches for a BehaviorTree with matching ID\n"
760 " 3. Links them together for execution\n"
761 " 4. Validates that the reference is valid\n\n"
762 "EXAMPLE:\n"
763 " <BehaviorTree ID=\"MainAI\">\n"
764 " <SubTree ID=\"PatrolBehavior\"/>\n"
765 " ^^^^^^^^^^^^^^^^^^^^^\n"
766 " This attribute references another tree\n"
767 " </BehaviorTree>\n\n"
768 "IMPORTANT:\n"
769 " - Should typically MATCH your Tree ID Attribute setting\n"
770 " - Referenced tree must exist in the same or loaded files\n"
771 " - Parser validates references during loading";
772 HelpDialog dlg(this, "SubTree Reference Attribute - Help", helpText);
773 dlg.ShowModal();
774 });
775 labelSizer5->Add(infoBtn5, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
776 labelSizer5->Add(new wxStaticText(panel, wxID_ANY, "SubTree Reference Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
777 sizer->Add(labelSizer5, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
778 m_subtreeRefAttr = new wxTextCtrl(panel, wxID_ANY);
779 m_subtreeRefAttr->SetToolTip("Attribute containing referenced tree ID (e.g., 'ID')");
781 sizer->Add(m_subtreeRefAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
782 row++;
783
784 // Validate structure
785 wxBoxSizer *labelSizer6 = new wxBoxSizer(wxHORIZONTAL);
786 wxBitmapButton *infoBtn6 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
787 wxBORDER_NONE | wxBU_EXACTFIT);
788 infoBtn6->SetBitmapHover(infoHover);
789 infoBtn6->SetBitmapPressed(infoPressed);
790 infoBtn6->SetBitmapDisabled(infoDisabled);
791 infoBtn6->SetToolTip("Click for detailed information");
792 infoBtn6->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
793 wxString helpText = "VALIDATE TREE STRUCTURE\n"
794 "===========================================================\n\n"
795 "Enables comprehensive structural validation to ensure your behavior\n"
796 "trees follow proper hierarchical and semantic rules.\n\n"
797 "ENABLED (Recommended):\n"
798 " The parser performs these critical checks:\n"
799 " - Control nodes (Sequence, Selector, etc.) have at least one child\n"
800 " - Decorator nodes have EXACTLY one child\n"
801 " - Action/Condition nodes have NO children\n"
802 " - No circular subtree references (A->B->A)\n"
803 " - All node types are properly nested\n"
804 " - Tree structure matches behavior tree semantics\n\n"
805 "DISABLED:\n"
806 " - No structural validation performed\n"
807 " - Faster parsing (minimal performance gain)\n"
808 " - MAY allow invalid trees that crash at runtime\n"
809 " - Use ONLY if you're 100% certain trees are valid\n\n"
810 "BENEFITS OF ENABLING:\n"
811 " - Catches errors EARLY during file loading\n"
812 " - Prevents mysterious runtime crashes\n"
813 " - Provides CLEAR, actionable error messages\n"
814 " - Saves debugging time\n\n"
815 "RECOMMENDATION:\n"
816 "Keep this ENABLED, especially during development!";
817 HelpDialog dlg(this, "Validate Tree Structure - Help", helpText);
818 dlg.ShowModal();
819 });
820 labelSizer6->Add(infoBtn6, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
821 m_validateStructure = new wxCheckBox(panel, wxID_ANY, "Validate Tree Structure");
822 m_validateStructure->SetToolTip("Perform structural validation on parsed trees");
823 labelSizer6->Add(m_validateStructure, 0, wxALIGN_CENTER_VERTICAL);
824 sizer->Add(labelSizer6, wxGBPosition(row, 0), wxGBSpan(1, 2));
825 row++;
826
827 // Require root node
828 wxBoxSizer *labelSizer7 = new wxBoxSizer(wxHORIZONTAL);
829 wxBitmapButton *infoBtn7 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
830 wxBORDER_NONE | wxBU_EXACTFIT);
831 infoBtn7->SetBitmapHover(infoHover);
832 infoBtn7->SetBitmapPressed(infoPressed);
833 infoBtn7->SetBitmapDisabled(infoDisabled);
834 infoBtn7->SetToolTip("Click for detailed information");
835 infoBtn7->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
836 wxString helpText = "REQUIRE ROOT NODE\n"
837 "===========================================================\n\n"
838 "Determines whether each behavior tree must have at least one\n"
839 "root node (a direct child of the BehaviorTree element).\n\n"
840 "ENABLED (Recommended):\n"
841 " - Each tree MUST contain at least one root-level node\n"
842 " - Empty trees are REJECTED during parsing\n"
843 " - Ensures all trees are executable and meaningful\n"
844 " - Catches incomplete or placeholder trees\n"
845 " - Standard for production-ready behavior trees\n\n"
846 "DISABLED:\n"
847 " - Empty trees are ALLOWED (no root node required)\n"
848 " - Useful for template/stub trees during development\n"
849 " - May allow non-functional tree structures\n"
850 " - Could lead to runtime issues if tree is called\n\n"
851 "EXAMPLE (VALID - with root):\n"
852 " <BehaviorTree ID=\"MainTree\">\n"
853 " <Sequence> <!-- ROOT NODE (direct child) -->\n"
854 " <Action name=\"DoSomething\"/>\n"
855 " </Sequence>\n"
856 " </BehaviorTree>\n\n"
857 "EXAMPLE (INVALID - no root):\n"
858 " <BehaviorTree ID=\"EmptyTree\">\n"
859 " <!-- No nodes! -->\n"
860 " </BehaviorTree>\n\n"
861 "RECOMMENDATION:\n"
862 "Keep ENABLED to ensure all trees are complete and executable!";
863 HelpDialog dlg(this, "Require Root Node - Help", helpText);
864 dlg.ShowModal();
865 });
866 labelSizer7->Add(infoBtn7, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
867 m_requireRootNode = new wxCheckBox(panel, wxID_ANY, "Require Root Node");
868 m_requireRootNode->SetToolTip("Each tree must have at least one root node");
869 labelSizer7->Add(m_requireRootNode, 0, wxALIGN_CENTER_VERTICAL);
870 sizer->Add(labelSizer7, wxGBPosition(row, 0), wxGBSpan(1, 2));
871 row++;
872
873 sizer->AddGrowableCol(1);
874 panel->SetSizer(sizer);
875 return panel;
876}
877
878wxPanel *ParserConfigDialog::CreateNodeElementsTab(wxNotebook *notebook) {
879 wxPanel *panel = new wxPanel(notebook);
880 wxGridBagSizer *sizer = new wxGridBagSizer(5, 5);
881
882 // Load info button bitmaps
883 wxBitmap infoNormal(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
884 wxBitmap infoHover(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
885 wxBitmap infoPressed(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
886 wxBitmap infoDisabled(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
887
888 int row = 0;
889
890 // Control element
891 wxBoxSizer *labelSizer1 = new wxBoxSizer(wxHORIZONTAL);
892 wxBitmapButton *infoBtn1 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
893 wxBORDER_NONE | wxBU_EXACTFIT);
894 infoBtn1->SetBitmapHover(infoHover);
895 infoBtn1->SetBitmapPressed(infoPressed);
896 infoBtn1->SetBitmapDisabled(infoDisabled);
897 infoBtn1->SetToolTip("Click for detailed information");
898 infoBtn1->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
899 wxString helpText = "CONTROL NODE ELEMENT\n"
900 "===========================================================\n\n"
901 "WHAT IS IT?\n"
902 "The XML element name that represents control flow nodes - nodes that\n"
903 "control the execution order and logic of their children.\n\n"
904 "COMMON VALUES:\n"
905 " - 'Control' - Generic control node element\n"
906 " - Leave empty to use specific names (Sequence, Selector, etc.)\n\n"
907 "CONTROL NODE TYPES:\n"
908 "Control nodes determine HOW and WHEN child nodes execute:\n"
909 " - Sequence: Execute children in order until one fails\n"
910 " - Selector/Fallback: Execute children until one succeeds\n"
911 " - Parallel: Execute multiple children simultaneously\n"
912 " - Random: Execute children in random order\n\n"
913 "HOW IT WORKS:\n"
914 "If set to 'Control':\n"
915 " <Control type=\"Sequence\">\n"
916 " <Action name=\"Step1\"/>\n"
917 " <Action name=\"Step2\"/>\n"
918 " </Control>\n\n"
919 "If left empty (common in BehaviorTree.CPP):\n"
920 " <Sequence> <!-- Direct element name -->\n"
921 " <Action name=\"Step1\"/>\n"
922 " </Sequence>\n\n"
923 "RECOMMENDATION:\n"
924 "For BehaviorTree.CPP compatibility, leave empty and use\n"
925 "specific element names like <Sequence>, <Selector>, etc.";
926 HelpDialog dlg(this, "Control Node Element - Help", helpText);
927 dlg.ShowModal();
928 });
929 labelSizer1->Add(infoBtn1, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
930 labelSizer1->Add(new wxStaticText(panel, wxID_ANY, "Control Node Element:"), 0, wxALIGN_CENTER_VERTICAL);
931 sizer->Add(labelSizer1, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
932 m_controlElement = new wxTextCtrl(panel, wxID_ANY);
933 m_controlElement->SetToolTip("Element name for control nodes (e.g., 'Control')");
935 sizer->Add(m_controlElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
936 row++;
937
938 // Action element
939 wxBoxSizer *labelSizer2 = new wxBoxSizer(wxHORIZONTAL);
940 wxBitmapButton *infoBtn2 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
941 wxBORDER_NONE | wxBU_EXACTFIT);
942 infoBtn2->SetBitmapHover(infoHover);
943 infoBtn2->SetBitmapPressed(infoPressed);
944 infoBtn2->SetBitmapDisabled(infoDisabled);
945 infoBtn2->SetToolTip("Click for detailed information");
946 infoBtn2->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
947 wxString helpText = "ACTION NODE ELEMENT\n"
948 "===========================================================\n\n"
949 "WHAT IS IT?\n"
950 "The XML element name that represents action nodes - nodes that\n"
951 "perform actual tasks or operations in your behavior tree.\n\n"
952 "COMMON VALUES:\n"
953 " - 'Action' - Standard BehaviorTree.CPP format\n"
954 " - 'Task' - Alternative naming\n"
955 " - 'Do' - Simple variant\n\n"
956 "WHAT ARE ACTION NODES?\n"
957 "Actions are the LEAF NODES that do the actual work:\n"
958 " - Move to a position\n"
959 " - Play an animation\n"
960 " - Attack an enemy\n"
961 " - Wait for a duration\n"
962 " - Set a variable\n\n"
963 "CHARACTERISTICS:\n"
964 " - Have NO children (leaf nodes)\n"
965 " - Return SUCCESS, FAILURE, or RUNNING\n"
966 " - Contain the actual executable logic\n"
967 " - May take parameters via attributes\n\n"
968 "EXAMPLE:\n"
969 " <BehaviorTree ID=\"PatrolAI\">\n"
970 " <Sequence>\n"
971 " <Action name=\"MoveToWaypoint\"/>\n"
972 " <Action name=\"Wait\" duration=\"2.0\"/>\n"
973 " <Action name=\"LookAround\"/>\n"
974 " </Sequence>\n"
975 " </BehaviorTree>\n\n"
976 "RECOMMENDATION:\n"
977 "Use 'Action' for standard BehaviorTree.CPP compatibility.";
978 HelpDialog dlg(this, "Action Node Element - Help", helpText);
979 dlg.ShowModal();
980 });
981 labelSizer2->Add(infoBtn2, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
982 labelSizer2->Add(new wxStaticText(panel, wxID_ANY, "Action Node Element:"), 0, wxALIGN_CENTER_VERTICAL);
983 sizer->Add(labelSizer2, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
984 m_actionElement = new wxTextCtrl(panel, wxID_ANY);
985 m_actionElement->SetToolTip("Element name for action nodes (e.g., 'Action')");
986 m_actionElement->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
987 sizer->Add(m_actionElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
988 row++;
989
990 // Condition element
991 wxBoxSizer *labelSizer3 = new wxBoxSizer(wxHORIZONTAL);
992 wxBitmapButton *infoBtn3 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
993 wxBORDER_NONE | wxBU_EXACTFIT);
994 infoBtn3->SetBitmapHover(infoHover);
995 infoBtn3->SetBitmapPressed(infoPressed);
996 infoBtn3->SetBitmapDisabled(infoDisabled);
997 infoBtn3->SetToolTip("Click for detailed information");
998 infoBtn3->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
999 wxString helpText = "CONDITION NODE ELEMENT\n"
1000 "===========================================================\n\n"
1001 "WHAT IS IT?\n"
1002 "The XML element name that represents condition nodes - nodes that\n"
1003 "check/test a state and return SUCCESS or FAILURE immediately.\n\n"
1004 "COMMON VALUES:\n"
1005 " - 'Condition' - Standard BehaviorTree.CPP format\n"
1006 " - 'Check' - Alternative naming\n"
1007 " - 'Test' - Some implementations\n\n"
1008 "WHAT ARE CONDITION NODES?\n"
1009 "Conditions are LEAF NODES that perform instant checks:\n"
1010 " - Is enemy in range?\n"
1011 " - Is health below 50%?\n"
1012 " - Is door open?\n"
1013 " - Has item in inventory?\n"
1014 " - Is time > X?\n\n"
1015 "KEY CHARACTERISTICS:\n"
1016 " - Have NO children (leaf nodes)\n"
1017 " - Execute INSTANTLY (never return RUNNING)\n"
1018 " - Return only SUCCESS or FAILURE\n"
1019 " - Used for decision-making logic\n"
1020 " - Often used with Selectors for branching\n\n"
1021 "EXAMPLE:\n"
1022 " <Selector>\n"
1023 " <Sequence>\n"
1024 " <Condition name=\"IsEnemyClose\"/>\n"
1025 " <Action name=\"Attack\"/>\n"
1026 " </Sequence>\n"
1027 " <Action name=\"Patrol\"/>\n"
1028 " </Selector>\n\n"
1029 "RECOMMENDATION:\n"
1030 "Use 'Condition' for standard BehaviorTree.CPP compatibility.";
1031 HelpDialog dlg(this, "Condition Node Element - Help", helpText);
1032 dlg.ShowModal();
1033 });
1034 labelSizer3->Add(infoBtn3, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1035 labelSizer3->Add(new wxStaticText(panel, wxID_ANY, "Condition Node Element:"), 0, wxALIGN_CENTER_VERTICAL);
1036 sizer->Add(labelSizer3, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1037 m_conditionElement = new wxTextCtrl(panel, wxID_ANY);
1038 m_conditionElement->SetToolTip("Element name for condition nodes (e.g., 'Condition')");
1040 sizer->Add(m_conditionElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1041 row++;
1042
1043 // Decorator element
1044 wxBoxSizer *labelSizer4 = new wxBoxSizer(wxHORIZONTAL);
1045 wxBitmapButton *infoBtn4 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1046 wxBORDER_NONE | wxBU_EXACTFIT);
1047 infoBtn4->SetBitmapHover(infoHover);
1048 infoBtn4->SetBitmapPressed(infoPressed);
1049 infoBtn4->SetBitmapDisabled(infoDisabled);
1050 infoBtn4->SetToolTip("Click for detailed information");
1051 infoBtn4->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1052 wxString helpText = "DECORATOR NODE ELEMENT\n"
1053 "===========================================================\n\n"
1054 "WHAT IS IT?\n"
1055 "The XML element name that represents decorator nodes - nodes that\n"
1056 "modify or enhance the behavior of EXACTLY ONE child node.\n\n"
1057 "COMMON VALUES:\n"
1058 " - 'Decorator' - Generic decorator element\n"
1059 " - Leave empty to use specific names (Inverter, Retry, etc.)\n\n"
1060 "WHAT ARE DECORATOR NODES?\n"
1061 "Decorators WRAP a single child and modify its behavior:\n"
1062 " - Inverter: Flip SUCCESS <-> FAILURE\n"
1063 " - Retry: Repeat child until success (N times)\n"
1064 " - Repeat: Execute child multiple times\n"
1065 " - Force Success/Failure: Override child's result\n"
1066 " - Timeout: Fail child if it takes too long\n"
1067 " - Delay: Wait before executing child\n\n"
1068 "KEY CHARACTERISTICS:\n"
1069 " - Must have EXACTLY ONE child\n"
1070 " - Act as a wrapper/modifier\n"
1071 " - Don't change the tree structure\n"
1072 " - Commonly used for logic inversion and retries\n\n"
1073 "EXAMPLE:\n"
1074 " <Sequence>\n"
1075 " <Inverter> <!-- Decorator: flips result -->\n"
1076 " <Condition name=\"IsEnemyClose\"/>\n"
1077 " </Inverter>\n"
1078 " <Action name=\"Patrol\"/>\n"
1079 " </Sequence>\n\n"
1080 "RECOMMENDATION:\n"
1081 "Leave empty and use specific names like <Inverter>, <Retry>.";
1082 HelpDialog dlg(this, "Decorator Node Element - Help", helpText);
1083 dlg.ShowModal();
1084 });
1085 labelSizer4->Add(infoBtn4, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1086 labelSizer4->Add(new wxStaticText(panel, wxID_ANY, "Decorator Node Element:"), 0, wxALIGN_CENTER_VERTICAL);
1087 sizer->Add(labelSizer4, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1088 m_decoratorElement = new wxTextCtrl(panel, wxID_ANY);
1089 m_decoratorElement->SetToolTip("Element name for decorator nodes (e.g., 'Decorator')");
1091 sizer->Add(m_decoratorElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1092 row++;
1093
1094 // Generic node element
1095 wxBoxSizer *labelSizer5 = new wxBoxSizer(wxHORIZONTAL);
1096 wxBitmapButton *infoBtn5 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1097 wxBORDER_NONE | wxBU_EXACTFIT);
1098 infoBtn5->SetBitmapHover(infoHover);
1099 infoBtn5->SetBitmapPressed(infoPressed);
1100 infoBtn5->SetBitmapDisabled(infoDisabled);
1101 infoBtn5->SetToolTip("Click for detailed information");
1102 infoBtn5->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1103 wxString helpText = "GENERIC NODE ELEMENT\n"
1104 "===========================================================\n\n"
1105 "WHAT IS IT?\n"
1106 "A fallback element name used when a node doesn't match any\n"
1107 "of the specific node type elements (Control, Action, etc.).\n\n"
1108 "COMMON VALUES:\n"
1109 " - 'Node' - Simple generic name\n"
1110 " - Leave empty if not needed\n\n"
1111 "WHEN IS IT USED?\n"
1112 "This acts as a catch-all for nodes that don't fit standard categories:\n"
1113 " - Custom node types specific to your implementation\n"
1114 " - Experimental or plugin nodes\n"
1115 " - Nodes that need runtime type determination\n\n"
1116 "HOW IT WORKS:\n"
1117 "Parser checks elements in this order:\n"
1118 " 1. Is it a Control node? (e.g., <Sequence>)\n"
1119 " 2. Is it an Action node? (e.g., <Action>)\n"
1120 " 3. Is it a Condition node? (e.g., <Condition>)\n"
1121 " 4. Is it a Decorator node? (e.g., <Inverter>)\n"
1122 " 5. Is it the Generic element? (e.g., <Node>)\n"
1123 " 6. Unknown element (error or ignored)\n\n"
1124 "EXAMPLE:\n"
1125 " <Node type=\"CustomPlugin\" name=\"SpecialBehavior\">\n"
1126 " <!-- Custom implementation -->\n"
1127 " </Node>\n\n"
1128 "RECOMMENDATION:\n"
1129 "Leave empty unless you have custom node types that don't\n"
1130 "fit the standard categories.";
1131 HelpDialog dlg(this, "Generic Node Element - Help", helpText);
1132 dlg.ShowModal();
1133 });
1134 labelSizer5->Add(infoBtn5, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1135 labelSizer5->Add(new wxStaticText(panel, wxID_ANY, "Generic Node Element:"), 0, wxALIGN_CENTER_VERTICAL);
1136 sizer->Add(labelSizer5, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1137 m_genericNodeElement = new wxTextCtrl(panel, wxID_ANY);
1138 m_genericNodeElement->SetToolTip("Fallback element name for generic nodes (e.g., 'Node')");
1140 sizer->Add(m_genericNodeElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1141 row++;
1142
1143 // Node ID attribute
1144 wxBoxSizer *labelSizer6 = new wxBoxSizer(wxHORIZONTAL);
1145 wxBitmapButton *infoBtn6 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1146 wxBORDER_NONE | wxBU_EXACTFIT);
1147 infoBtn6->SetBitmapHover(infoHover);
1148 infoBtn6->SetBitmapPressed(infoPressed);
1149 infoBtn6->SetBitmapDisabled(infoDisabled);
1150 infoBtn6->SetToolTip("Click for detailed information");
1151 infoBtn6->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1152 wxString helpText = "NODE ID ATTRIBUTE\n"
1153 "===========================================================\n\n"
1154 "WHAT IS IT?\n"
1155 "The XML attribute name that identifies the specific TYPE or\n"
1156 "CLASS of a node (e.g., 'Sequence', 'MoveToTarget', 'IsHealthLow').\n\n"
1157 "COMMON VALUES:\n"
1158 " - 'ID' - BehaviorTree.CPP standard\n"
1159 " - 'type' - Alternative common usage\n"
1160 " - 'class' - Some implementations\n\n"
1161 "WHAT DOES IT CONTAIN?\n"
1162 "The ID typically specifies the node's TYPE/CLASS:\n"
1163 " - For Control: 'Sequence', 'Selector', 'Parallel'\n"
1164 " - For Action: 'MoveToTarget', 'Attack', 'PlayAnimation'\n"
1165 " - For Condition: 'IsHealthLow', 'IsEnemyNear'\n"
1166 " - For Decorator: 'Inverter', 'Retry', 'Timeout'\n\n"
1167 "EXAMPLE (using ID='type'):\n"
1168 " <Control ID=\"Sequence\">\n"
1169 " <Action ID=\"MoveToWaypoint\" target=\"wp1\"/>\n"
1170 " <Action ID=\"Wait\" duration=\"2.0\"/>\n"
1171 " </Control>\n\n"
1172 "vs EXAMPLE (element name is type):\n"
1173 " <Sequence>\n"
1174 " <MoveToWaypoint target=\"wp1\"/>\n"
1175 " <Wait duration=\"2.0\"/>\n"
1176 " </Sequence>\n\n"
1177 "RECOMMENDATION:\n"
1178 "Use 'ID' for BehaviorTree.CPP compatibility when using\n"
1179 "generic element names. Leave empty if using specific\n"
1180 "element names for each node type.";
1181 HelpDialog dlg(this, "Node ID Attribute - Help", helpText);
1182 dlg.ShowModal();
1183 });
1184 labelSizer6->Add(infoBtn6, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1185 labelSizer6->Add(new wxStaticText(panel, wxID_ANY, "Node ID Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
1186 sizer->Add(labelSizer6, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1187 m_nodeIdAttr = new wxTextCtrl(panel, wxID_ANY);
1188 m_nodeIdAttr->SetToolTip("Attribute name for node identifier/type (e.g., 'ID')");
1189 m_nodeIdAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
1190 sizer->Add(m_nodeIdAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1191 row++;
1192
1193 // Node name attribute
1194 wxBoxSizer *labelSizer7 = new wxBoxSizer(wxHORIZONTAL);
1195 wxBitmapButton *infoBtn7 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1196 wxBORDER_NONE | wxBU_EXACTFIT);
1197 infoBtn7->SetBitmapHover(infoHover);
1198 infoBtn7->SetBitmapPressed(infoPressed);
1199 infoBtn7->SetBitmapDisabled(infoDisabled);
1200 infoBtn7->SetToolTip("Click for detailed information");
1201 infoBtn7->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1202 wxString helpText = "NODE NAME ATTRIBUTE\n"
1203 "===========================================================\n\n"
1204 "WHAT IS IT?\n"
1205 "The XML attribute name that provides a human-readable display\n"
1206 "name or label for a node instance.\n\n"
1207 "COMMON VALUES:\n"
1208 " - 'name' - Most common standard\n"
1209 " - 'label' - Alternative\n"
1210 " - 'displayName' - Explicit variant\n\n"
1211 "WHAT'S THE DIFFERENCE FROM NODE ID?\n"
1212 " - Node ID: The TYPE/CLASS of the node (e.g., 'MoveToTarget')\n"
1213 " - Node Name: A specific INSTANCE label (e.g., 'Move to Enemy')\n\n"
1214 "WHY USE IT?\n"
1215 " - Provide descriptive labels in visual editors\n"
1216 " - Make trees more readable in XML\n"
1217 " - Help with debugging and logging\n"
1218 " - Distinguish between multiple instances of same node type\n\n"
1219 "EXAMPLE:\n"
1220 " <Action ID=\"MoveToTarget\" name=\"Move to Waypoint 1\">\n"
1221 " ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^\n"
1222 " Node type/class Human-readable name\n"
1223 "\n"
1224 " <Action ID=\"MoveToTarget\" name=\"Return to Base\">\n"
1225 " Same type Different instance name\n\n"
1226 "IN VISUAL EDITOR:\n"
1227 "The 'name' would be displayed on the node box, making the\n"
1228 "tree easier to understand at a glance.\n\n"
1229 "RECOMMENDATION:\n"
1230 "Use 'name' for standard compatibility and readability.";
1231 HelpDialog dlg(this, "Node Name Attribute - Help", helpText);
1232 dlg.ShowModal();
1233 });
1234 labelSizer7->Add(infoBtn7, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1235 labelSizer7->Add(new wxStaticText(panel, wxID_ANY, "Node Name Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
1236 sizer->Add(labelSizer7, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1237 m_nodeNameAttr = new wxTextCtrl(panel, wxID_ANY);
1238 m_nodeNameAttr->SetToolTip("Attribute name for node display name (e.g., 'name')");
1239 m_nodeNameAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
1240 sizer->Add(m_nodeNameAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1241 row++;
1242
1243 // Node type attribute
1244 wxBoxSizer *labelSizer8 = new wxBoxSizer(wxHORIZONTAL);
1245 wxBitmapButton *infoBtn8 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1246 wxBORDER_NONE | wxBU_EXACTFIT);
1247 infoBtn8->SetBitmapHover(infoHover);
1248 infoBtn8->SetBitmapPressed(infoPressed);
1249 infoBtn8->SetBitmapDisabled(infoDisabled);
1250 infoBtn8->SetToolTip("Click for detailed information");
1251 infoBtn8->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1252 wxString helpText = "NODE TYPE ATTRIBUTE\n"
1253 "===========================================================\n\n"
1254 "WHAT IS IT?\n"
1255 "An ALTERNATIVE attribute used for by-attribute node classification,\n"
1256 "where node category is specified via attribute instead of element name.\n\n"
1257 "COMMON VALUES:\n"
1258 " - 'type' - Most common\n"
1259 " - 'category' - Alternative\n"
1260 " - 'nodeType' - Explicit variant\n\n"
1261 "WHEN IS THIS USED?\n"
1262 "Some XML formats use a SINGLE generic element with a type attribute\n"
1263 "instead of different elements for each node category:\n\n"
1264 "BY-ELEMENT APPROACH (most common):\n"
1265 " <Sequence>\n"
1266 " <Action name=\"Move\"/>\n"
1267 " <Condition name=\"IsClose\"/>\n"
1268 " </Sequence>\n\n"
1269 "BY-ATTRIBUTE APPROACH:\n"
1270 " <Node type=\"Sequence\">\n"
1271 " <Node type=\"Action\" name=\"Move\"/>\n"
1272 " <Node type=\"Condition\" name=\"IsClose\"/>\n"
1273 " </Node>\n"
1274 " ^^^^^^^^^^^^^^\n"
1275 " This attribute specifies node category\n\n"
1276 "ATTRIBUTE VALUES:\n"
1277 " - 'Control', 'Action', 'Condition', 'Decorator'\n"
1278 " - Used by parser to classify nodes\n\n"
1279 "RECOMMENDATION:\n"
1280 "Only use this if your XML format uses by-attribute\n"
1281 "classification. Leave empty for standard element-based\n"
1282 "classification (BehaviorTree.CPP style).";
1283 HelpDialog dlg(this, "Node Type Attribute - Help", helpText);
1284 dlg.ShowModal();
1285 });
1286 labelSizer8->Add(infoBtn8, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1287 labelSizer8->Add(new wxStaticText(panel, wxID_ANY, "Node Type Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
1288 sizer->Add(labelSizer8, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1289 m_nodeTypeAttr = new wxTextCtrl(panel, wxID_ANY);
1290 m_nodeTypeAttr->SetToolTip("Attribute name for node type (used in by-attribute classification)");
1291 m_nodeTypeAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
1292 sizer->Add(m_nodeTypeAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1293 row++;
1294
1295 // Allow custom attributes
1296 wxBoxSizer *labelSizer9 = new wxBoxSizer(wxHORIZONTAL);
1297 wxBitmapButton *infoBtn9 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1298 wxBORDER_NONE | wxBU_EXACTFIT);
1299 infoBtn9->SetBitmapHover(infoHover);
1300 infoBtn9->SetBitmapPressed(infoPressed);
1301 infoBtn9->SetBitmapDisabled(infoDisabled);
1302 infoBtn9->SetToolTip("Click for detailed information");
1303 infoBtn9->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1304 wxString helpText = "ALLOW CUSTOM ATTRIBUTES\n"
1305 "===========================================================\n\n"
1306 "Controls whether the parser preserves and stores custom/additional\n"
1307 "attributes found on nodes beyond the standard configuration attributes.\n\n"
1308 "ENABLED (Recommended):\n"
1309 " - ALL attributes on nodes are parsed and stored\n"
1310 " - Custom parameters are preserved for runtime use\n"
1311 " - Enables flexible node parameterization\n"
1312 " - Required for most practical behavior trees\n\n"
1313 "DISABLED:\n"
1314 " - Only recognized/standard attributes are stored\n"
1315 " - Custom attributes are IGNORED during parsing\n"
1316 " - Stricter validation, but less flexible\n"
1317 " - May lose important node parameters\n\n"
1318 "EXAMPLE (custom attributes in bold concept):\n"
1319 " <Action name=\"MoveToTarget\" \n"
1320 " speed=\"5.0\" <!-- Custom attribute -->\n"
1321 " target=\"enemy\" <!-- Custom attribute -->\n"
1322 " acceptableRadius=\"2.0\"> <!-- Custom -->\n\n"
1323 "WHY ENABLE?\n"
1324 " - Actions/Conditions need parameters to work\n"
1325 " - Different node instances need different values\n"
1326 " - Runtime system reads these for behavior\n"
1327 " - Standard in BehaviorTree.CPP and most frameworks\n\n"
1328 "RECOMMENDATION:\n"
1329 "KEEP THIS ENABLED! Most behavior trees require custom\n"
1330 "attributes for node parameters, making trees functional\n"
1331 "and configurable.";
1332 HelpDialog dlg(this, "Allow Custom Attributes - Help", helpText);
1333 dlg.ShowModal();
1334 });
1335 labelSizer9->Add(infoBtn9, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1336 m_allowCustomAttrs = new wxCheckBox(panel, wxID_ANY, "Allow Custom Attributes");
1337 m_allowCustomAttrs->SetToolTip("Parse and store custom attributes on nodes");
1338 labelSizer9->Add(m_allowCustomAttrs, 0, wxALIGN_CENTER_VERTICAL);
1339 sizer->Add(labelSizer9, wxGBPosition(row, 0), wxGBSpan(1, 2));
1340 row++;
1341
1342 sizer->AddGrowableCol(1);
1343 panel->SetSizer(sizer);
1344 return panel;
1345}
1346
1348 wxPanel *panel = new wxPanel(notebook);
1349 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
1350
1351 // Load info button bitmaps
1352 wxBitmap infoNormal(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
1353 wxBitmap infoHover(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
1354 wxBitmap infoPressed(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
1355 wxBitmap infoDisabled(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
1356
1357 // Classification strategy
1358 wxBoxSizer *strategySizer = new wxBoxSizer(wxHORIZONTAL);
1359
1360 wxBitmapButton *infoBtn1 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1361 wxBORDER_NONE | wxBU_EXACTFIT);
1362 infoBtn1->SetBitmapHover(infoHover);
1363 infoBtn1->SetBitmapPressed(infoPressed);
1364 infoBtn1->SetBitmapDisabled(infoDisabled);
1365 infoBtn1->SetToolTip("Click for detailed information");
1366 infoBtn1->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1367 wxString helpText = "CLASSIFICATION STRATEGY\n"
1368 "===========================================================\n\n"
1369 "Determines HOW the parser identifies what type of node it's dealing\n"
1370 "with when reading your XML.\n\n"
1371 "THREE STRATEGIES:\n\n"
1372 "1. BY ELEMENT NAME (Most Common)\n"
1373 " - Node type is determined by the XML element name itself\n"
1374 " - Different elements for different types\n"
1375 " Example:\n"
1376 " <Sequence> <!-- Control node -->\n"
1377 " <Action name=\"Move\"/> <!-- Action node -->\n"
1378 " <Condition name=\"Check\"/> <!-- Condition node -->\n"
1379 " </Sequence>\n"
1380 " Pros: Clean, readable XML; standard in BehaviorTree.CPP\n"
1381 " Use when: You want clear, self-documenting trees\n\n"
1382 "2. BY ATTRIBUTE\n"
1383 " - Single generic element with 'type' attribute\n"
1384 " - All nodes use the same element name\n"
1385 " Example:\n"
1386 " <Node type=\"Sequence\">\n"
1387 " <Node type=\"Action\" name=\"Move\"/>\n"
1388 " <Node type=\"Condition\" name=\"Check\"/>\n"
1389 " </Node>\n"
1390 " Pros: Uniform structure; easier schema validation\n"
1391 " Use when: You need strict XML schema conformance\n\n"
1392 "3. HYBRID\n"
1393 " - Mix of both approaches\n"
1394 " - Checks element name first, falls back to attribute\n"
1395 " Example:\n"
1396 " <Sequence> <!-- By element name -->\n"
1397 " <Node type=\"Action\" name=\"Custom\"/> <!-- By attribute -->\n"
1398 " <Condition name=\"Check\"/> <!-- By element name -->\n"
1399 " </Sequence>\n"
1400 " Pros: Maximum flexibility; supports mixed formats\n"
1401 " Use when: Working with multiple XML formats\n\n"
1402 "RECOMMENDATION:\n"
1403 "Use 'By Element Name' for standard BehaviorTree.CPP compatibility\n"
1404 "and maximum readability.";
1405 HelpDialog dlg(this, "Classification Strategy - Help", helpText);
1406 dlg.ShowModal();
1407 });
1408 strategySizer->Add(infoBtn1, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
1409 strategySizer->Add(new wxStaticText(panel, wxID_ANY, "Classification Strategy:"), 0,
1410 wxALIGN_CENTER_VERTICAL | wxALL, 5);
1411 wxArrayString strategyChoices;
1412 strategyChoices.Add("By Element Name");
1413 strategyChoices.Add("By Attribute");
1414 strategyChoices.Add("Hybrid");
1415 m_classificationStrategy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, strategyChoices);
1416 m_classificationStrategy->SetSelection(0);
1417 strategySizer->Add(m_classificationStrategy, 1, wxALL, 5);
1418 mainSizer->Add(strategySizer, 0, wxEXPAND);
1419
1420 // Type lists section header with info button
1421 wxBoxSizer *listsHeaderSizer = new wxBoxSizer(wxHORIZONTAL);
1422
1423 // Reload bitmaps for this button
1424 wxBitmap infoNormal4(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
1425 wxBitmap infoHover4(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
1426 wxBitmap infoPressed4(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
1427 wxBitmap infoDisabled4(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
1428
1429 wxBitmapButton *infoBtn4 = new wxBitmapButton(panel, wxID_ANY, infoNormal4, wxDefaultPosition, wxSize(40, 40),
1430 wxBORDER_NONE | wxBU_EXACTFIT);
1431 infoBtn4->SetBitmapHover(infoHover4);
1432 infoBtn4->SetBitmapPressed(infoPressed4);
1433 infoBtn4->SetBitmapDisabled(infoDisabled4);
1434 infoBtn4->SetToolTip("Click for detailed information");
1435 infoBtn4->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1436 wxString helpText = "NODE TYPE LISTS\n"
1437 "===========================================================\n\n"
1438 "These lists define the SPECIFIC NODE TYPES recognized in each\n"
1439 "category. Used for element-name and attribute-based classification.\n\n"
1440 "HOW THEY WORK:\n"
1441 "When the parser encounters a node, it checks if the node's name\n"
1442 "(element or attribute value) appears in one of these lists to\n"
1443 "determine its category.\n\n"
1444 "FOUR CATEGORIES:\n\n"
1445 "1. CONTROL TYPES (Required)\n"
1446 " Flow control nodes that manage child execution\n"
1447 " Common examples:\n"
1448 " - Sequence, Selector, Fallback\n"
1449 " - Parallel, ParallelSequence\n"
1450 " - ReactiveSequence, ReactiveFallback\n"
1451 " - IfThenElse, Switch, WhileDoElse\n"
1452 " These nodes MUST have at least one child\n\n"
1453 "2. DECORATOR TYPES (Required)\n"
1454 " Wrapper nodes that modify a single child's behavior\n"
1455 " Common examples:\n"
1456 " - Inverter, ForceSuccess, ForceFailure\n"
1457 " - Retry, Repeat, RepeatUntilSuccess\n"
1458 " - Timeout, Delay\n"
1459 " - KeepRunningUntilFailure\n"
1460 " These nodes MUST have exactly one child\n\n"
1461 "3. ACTION TYPES (Optional)\n"
1462 " Leaf nodes that perform tasks\n"
1463 " Common examples:\n"
1464 " - MoveToTarget, Attack, Wait\n"
1465 " - PlayAnimation, SetBlackboard\n"
1466 " - Custom action nodes\n"
1467 " Leave empty to allow any non-classified leaf as Action\n\n"
1468 "4. CONDITION TYPES (Optional)\n"
1469 " Leaf nodes that check state (instant, no RUNNING)\n"
1470 " Common examples:\n"
1471 " - IsHealthLow, IsEnemyInRange\n"
1472 " - CheckBlackboard, IsBoolTrue\n"
1473 " - Custom condition nodes\n"
1474 " Leave empty to allow any non-classified leaf as Condition\n\n"
1475 "ADDING ENTRIES:\n"
1476 " 1. Click '+' button\n"
1477 " 2. Enter the exact node type name\n"
1478 " 3. Case-sensitive (usually)\n\n"
1479 "EXAMPLE CLASSIFICATION:\n"
1480 " Lists: Control=[Sequence, Selector], Decorator=[Inverter]\n"
1481 " XML: <Sequence> -> Control (in list)\n"
1482 " XML: <Inverter> -> Decorator (in list)\n"
1483 " XML: <CustomAction> -> Action (not in any list)\n\n"
1484 "RECOMMENDATION:\n"
1485 "Always define Control and Decorator types. Leave Action/Condition\n"
1486 "empty for maximum flexibility unless you need strict validation.";
1487 HelpDialog dlg(this, "Node Type Lists - Help", helpText);
1488 dlg.ShowModal();
1489 });
1490 listsHeaderSizer->Add(infoBtn4, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
1491 wxStaticText *listsLabel = new wxStaticText(panel, wxID_ANY, "Node Type Recognition Lists:");
1492 wxFont font = listsLabel->GetFont();
1493 font.SetWeight(wxFONTWEIGHT_BOLD);
1494 listsLabel->SetFont(font);
1495 listsHeaderSizer->Add(listsLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
1496 mainSizer->Add(listsHeaderSizer, 0, wxEXPAND);
1497
1498 // Type lists in a grid
1499 wxBoxSizer *listsGrid = new wxBoxSizer(wxHORIZONTAL);
1500
1501 // Control types
1502 wxStaticBoxSizer *controlBox = new wxStaticBoxSizer(wxVERTICAL, panel, "Control Types");
1503 m_controlTypesList = new wxListBox(controlBox->GetStaticBox(), wxID_ANY);
1504 controlBox->Add(m_controlTypesList, 1, wxEXPAND | wxALL, 5);
1505 wxBoxSizer *controlBtnSizer = new wxBoxSizer(wxHORIZONTAL);
1506 m_addControlTypeBtn = new wxButton(controlBox->GetStaticBox(), ID_ADD_CONTROL_TYPE, "+");
1507 m_removeControlTypeBtn = new wxButton(controlBox->GetStaticBox(), ID_REMOVE_CONTROL_TYPE, "-");
1508 controlBtnSizer->Add(m_addControlTypeBtn, 1, wxALL, 2);
1509 controlBtnSizer->Add(m_removeControlTypeBtn, 1, wxALL, 2);
1510 controlBox->Add(controlBtnSizer, 0, wxEXPAND);
1511 listsGrid->Add(controlBox, 1, wxEXPAND | wxALL, 5);
1512
1513 // Decorator types
1514 wxStaticBoxSizer *decoratorBox = new wxStaticBoxSizer(wxVERTICAL, panel, "Decorator Types");
1515 m_decoratorTypesList = new wxListBox(decoratorBox->GetStaticBox(), wxID_ANY);
1516 decoratorBox->Add(m_decoratorTypesList, 1, wxEXPAND | wxALL, 5);
1517 wxBoxSizer *decoratorBtnSizer = new wxBoxSizer(wxHORIZONTAL);
1518 m_addDecoratorTypeBtn = new wxButton(decoratorBox->GetStaticBox(), ID_ADD_DECORATOR_TYPE, "+");
1519 m_removeDecoratorTypeBtn = new wxButton(decoratorBox->GetStaticBox(), ID_REMOVE_DECORATOR_TYPE, "-");
1520 decoratorBtnSizer->Add(m_addDecoratorTypeBtn, 1, wxALL, 2);
1521 decoratorBtnSizer->Add(m_removeDecoratorTypeBtn, 1, wxALL, 2);
1522 decoratorBox->Add(decoratorBtnSizer, 0, wxEXPAND);
1523 listsGrid->Add(decoratorBox, 1, wxEXPAND | wxALL, 5);
1524
1525 mainSizer->Add(listsGrid, 1, wxEXPAND);
1526
1527 // Second row: Action and Condition types
1528 wxBoxSizer *listsGrid2 = new wxBoxSizer(wxHORIZONTAL);
1529
1530 // Action types
1531 wxStaticBoxSizer *actionBox = new wxStaticBoxSizer(wxVERTICAL, panel, "Action Types (optional)");
1532 m_actionTypesList = new wxListBox(actionBox->GetStaticBox(), wxID_ANY);
1533 actionBox->Add(m_actionTypesList, 1, wxEXPAND | wxALL, 5);
1534 wxBoxSizer *actionBtnSizer = new wxBoxSizer(wxHORIZONTAL);
1535 m_addActionTypeBtn = new wxButton(actionBox->GetStaticBox(), ID_ADD_ACTION_TYPE, "+");
1536 m_removeActionTypeBtn = new wxButton(actionBox->GetStaticBox(), ID_REMOVE_ACTION_TYPE, "-");
1537 actionBtnSizer->Add(m_addActionTypeBtn, 1, wxALL, 2);
1538 actionBtnSizer->Add(m_removeActionTypeBtn, 1, wxALL, 2);
1539 actionBox->Add(actionBtnSizer, 0, wxEXPAND);
1540 listsGrid2->Add(actionBox, 1, wxEXPAND | wxALL, 5);
1541
1542 // Condition types
1543 wxStaticBoxSizer *conditionBox = new wxStaticBoxSizer(wxVERTICAL, panel, "Condition Types (optional)");
1544 m_conditionTypesList = new wxListBox(conditionBox->GetStaticBox(), wxID_ANY);
1545 conditionBox->Add(m_conditionTypesList, 1, wxEXPAND | wxALL, 5);
1546 wxBoxSizer *conditionBtnSizer = new wxBoxSizer(wxHORIZONTAL);
1547 m_addConditionTypeBtn = new wxButton(conditionBox->GetStaticBox(), ID_ADD_CONDITION_TYPE, "+");
1548 m_removeConditionTypeBtn = new wxButton(conditionBox->GetStaticBox(), ID_REMOVE_CONDITION_TYPE, "-");
1549 conditionBtnSizer->Add(m_addConditionTypeBtn, 1, wxALL, 2);
1550 conditionBtnSizer->Add(m_removeConditionTypeBtn, 1, wxALL, 2);
1551 conditionBox->Add(conditionBtnSizer, 0, wxEXPAND);
1552 listsGrid2->Add(conditionBox, 1, wxEXPAND | wxALL, 5);
1553
1554 mainSizer->Add(listsGrid2, 1, wxEXPAND);
1555
1556 // Unknown behavior
1557 wxBoxSizer *unknownSizer = new wxBoxSizer(wxHORIZONTAL);
1558
1559 // Reload bitmaps for this button
1560 wxBitmap infoNormal2(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
1561 wxBitmap infoHover2(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
1562 wxBitmap infoPressed2(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
1563 wxBitmap infoDisabled2(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
1564
1565 wxBitmapButton *infoBtn2 = new wxBitmapButton(panel, wxID_ANY, infoNormal2, wxDefaultPosition, wxSize(40, 40),
1566 wxBORDER_NONE | wxBU_EXACTFIT);
1567 infoBtn2->SetBitmapHover(infoHover2);
1568 infoBtn2->SetBitmapPressed(infoPressed2);
1569 infoBtn2->SetBitmapDisabled(infoDisabled2);
1570 infoBtn2->SetToolTip("Click for detailed information");
1571 infoBtn2->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1572 wxString helpText = "UNKNOWN TYPE BEHAVIOR\n"
1573 "===========================================================\n\n"
1574 "Determines what happens when the parser encounters a node type\n"
1575 "that is NOT in your defined type lists (Control, Decorator, etc.).\n\n"
1576 "THREE OPTIONS:\n\n"
1577 "1. ERROR (Strict - Recommended for Development)\n"
1578 " - Parser STOPS and reports an error\n"
1579 " - File loading FAILS\n"
1580 " - Ensures all node types are explicitly defined\n"
1581 " Example: <UnknownNode/> causes parsing to fail\n"
1582 " Use when:\n"
1583 " - Developing new trees - catch typos early\n"
1584 " - Want strict validation\n"
1585 " - Need to ensure only known types are used\n\n"
1586 "2. WARNING (Permissive)\n"
1587 " - Parser CONTINUES but logs a warning\n"
1588 " - File loads successfully\n"
1589 " - Unknown node is treated as Generic Node\n"
1590 " Example: <UnknownNode/> becomes a generic node + warning\n"
1591 " Use when:\n"
1592 " - Working with mixed/evolving formats\n"
1593 " - Want to see what's unrecognized but still load\n"
1594 " - Debugging format compatibility\n\n"
1595 "3. GENERIC NODE (Silent Fallback)\n"
1596 " - Parser treats unknown types as generic nodes\n"
1597 " - NO warning or error\n"
1598 " - Maximum compatibility\n"
1599 " Example: <UnknownNode/> silently becomes generic node\n"
1600 " Use when:\n"
1601 " - Working with highly variable XML formats\n"
1602 " - Want maximum flexibility\n"
1603 " - Already validated your trees externally\n\n"
1604 "RECOMMENDATION:\n"
1605 "Use ERROR during development to catch typos and undefined types.\n"
1606 "Use WARNING in production if you need to support evolving formats.";
1607 HelpDialog dlg(this, "Unknown Type Behavior - Help", helpText);
1608 dlg.ShowModal();
1609 });
1610 unknownSizer->Add(infoBtn2, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
1611 unknownSizer->Add(new wxStaticText(panel, wxID_ANY, "Unknown Type Behavior:"), 0, wxALIGN_CENTER_VERTICAL | wxALL,
1612 5);
1613 wxArrayString unknownChoices;
1614 unknownChoices.Add("Error");
1615 unknownChoices.Add("Warning");
1616 unknownChoices.Add("Generic Node");
1617 m_unknownBehavior = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, unknownChoices);
1618 m_unknownBehavior->SetSelection(0);
1619 unknownSizer->Add(m_unknownBehavior, 1, wxALL, 5);
1620 mainSizer->Add(unknownSizer, 0, wxEXPAND);
1621
1622 // Type attribute name
1623 wxBoxSizer *typeAttrSizer = new wxBoxSizer(wxHORIZONTAL);
1624
1625 // Reload bitmaps for this button
1626 wxBitmap infoNormal3(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
1627 wxBitmap infoHover3(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
1628 wxBitmap infoPressed3(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
1629 wxBitmap infoDisabled3(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
1630
1631 wxBitmapButton *infoBtn3 = new wxBitmapButton(panel, wxID_ANY, infoNormal3, wxDefaultPosition, wxSize(40, 40),
1632 wxBORDER_NONE | wxBU_EXACTFIT);
1633 infoBtn3->SetBitmapHover(infoHover3);
1634 infoBtn3->SetBitmapPressed(infoPressed3);
1635 infoBtn3->SetBitmapDisabled(infoDisabled3);
1636 infoBtn3->SetToolTip("Click for detailed information");
1637 infoBtn3->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1638 wxString helpText = "TYPE ATTRIBUTE NAME\n"
1639 "===========================================================\n\n"
1640 "The XML attribute name used for by-attribute node classification.\n"
1641 "Only relevant when using 'By Attribute' or 'Hybrid' strategy.\n\n"
1642 "WHAT IS IT?\n"
1643 "When using attribute-based classification, this attribute specifies\n"
1644 "the node category (Control, Action, Condition, Decorator).\n\n"
1645 "COMMON VALUES:\n"
1646 " - 'type' - Most common standard\n"
1647 " - 'nodeType' - Explicit variant\n"
1648 " - 'category' - Alternative naming\n\n"
1649 "HOW IT WORKS:\n\n"
1650 "BY ATTRIBUTE STRATEGY:\n"
1651 " <Node type=\"Sequence\"> <!-- 'type' is the attribute -->\n"
1652 " <Node type=\"Action\" name=\"Move\"/>\n"
1653 " <Node type=\"Condition\" name=\"Check\"/>\n"
1654 " </Node>\n"
1655 " The parser reads the 'type' attribute to determine:\n"
1656 " - 'Sequence' -> Control node (from Control Types list)\n"
1657 " - 'Action' -> Action node (from Action Types list)\n"
1658 " - 'Condition' -> Condition node (from Condition Types list)\n\n"
1659 "HYBRID STRATEGY:\n"
1660 " <Sequence> <!-- Element name = Control -->\n"
1661 " <Node type=\"Action\" name=\"Custom\"/> <!-- Attribute -->\n"
1662 " <Condition name=\"Check\"/> <!-- Element name -->\n"
1663 " </Sequence>\n"
1664 " Parser checks element name first, falls back to attribute.\n\n"
1665 "BY ELEMENT NAME STRATEGY:\n"
1666 " This setting is IGNORED (not used)\n\n"
1667 "CLASSIFICATION FLOW:\n"
1668 " 1. Read node's type attribute value\n"
1669 " 2. Check if value is in Control Types -> Control node\n"
1670 " 3. Check if value is in Decorator Types -> Decorator node\n"
1671 " 4. Check if value is in Action Types -> Action node\n"
1672 " 5. Check if value is in Condition Types -> Condition node\n"
1673 " 6. Not found -> Apply Unknown Type Behavior\n\n"
1674 "RECOMMENDATION:\n"
1675 "Use 'type' for standard compatibility. Leave empty if using\n"
1676 "'By Element Name' strategy exclusively.";
1677 HelpDialog dlg(this, "Type Attribute Name - Help", helpText);
1678 dlg.ShowModal();
1679 });
1680 typeAttrSizer->Add(infoBtn3, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
1681 typeAttrSizer->Add(new wxStaticText(panel, wxID_ANY, "Type Attribute Name:"), 0, wxALIGN_CENTER_VERTICAL | wxALL,
1682 5);
1683 m_typeAttrName = new wxTextCtrl(panel, wxID_ANY);
1684 m_typeAttrName->SetToolTip("Attribute name used for by-attribute classification");
1685 m_typeAttrName->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
1686 typeAttrSizer->Add(m_typeAttrName, 1, wxALL, 5);
1687 mainSizer->Add(typeAttrSizer, 0, wxEXPAND);
1688
1689 panel->SetSizer(mainSizer);
1690 return panel;
1691}
1692
1693wxPanel *ParserConfigDialog::CreateBlackboardTab(wxNotebook *notebook) {
1694 wxPanel *panel = new wxPanel(notebook);
1695 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
1696
1697 // Load info button bitmaps
1698 wxBitmap infoNormal(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
1699 wxBitmap infoHover(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
1700 wxBitmap infoPressed(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
1701 wxBitmap infoDisabled(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
1702
1703 // Basic blackboard configuration
1704 wxGridBagSizer *basicSizer = new wxGridBagSizer(5, 5);
1705 int row = 0;
1706
1707 // Blackboard element
1708 wxBoxSizer *labelSizer1 = new wxBoxSizer(wxHORIZONTAL);
1709 wxBitmapButton *infoBtn1 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1710 wxBORDER_NONE | wxBU_EXACTFIT);
1711 infoBtn1->SetBitmapHover(infoHover);
1712 infoBtn1->SetBitmapPressed(infoPressed);
1713 infoBtn1->SetBitmapDisabled(infoDisabled);
1714 infoBtn1->SetToolTip("Click for detailed information");
1715 infoBtn1->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1716 wxString helpText = "BLACKBOARD ELEMENT\n"
1717 "===========================================================\n\n"
1718 "WHAT IS IT?\n"
1719 "The XML element name that represents a blackboard definition - a\n"
1720 "shared data storage that behavior trees use to store and access variables.\n\n"
1721 "COMMON VALUES:\n"
1722 " - 'BlackboardModel' - BehaviorTree.CPP standard\n"
1723 " - 'Blackboard' - Simplified variant\n"
1724 " - 'Variables' - Alternative naming\n\n"
1725 "WHAT IS A BLACKBOARD?\n"
1726 "A blackboard is a KEY-VALUE data store that:\n"
1727 " - Stores variables shared across the behavior tree\n"
1728 " - Provides data for condition checks and actions\n"
1729 " - Can be defined once and used by multiple trees\n"
1730 " - Supports typed data (int, float, string, bool, etc.)\n\n"
1731 "EXAMPLE:\n"
1732 " <root>\n"
1733 " <BlackboardModel ID=\"PlayerBlackboard\">\n"
1734 " <Entry key=\"health\" type=\"int\" value=\"100\"/>\n"
1735 " <Entry key=\"target\" type=\"string\"/>\n"
1736 " <Entry key=\"isAggressive\" type=\"bool\" value=\"false\"/>\n"
1737 " </BlackboardModel>\n\n"
1738 " <BehaviorTree ID=\"MainAI\" blackboard=\"PlayerBlackboard\">\n"
1739 " <!-- Tree uses the blackboard -->\n"
1740 " </BehaviorTree>\n"
1741 " </root>\n\n"
1742 "WHY USE BLACKBOARDS?\n"
1743 " - Share data between different parts of the tree\n"
1744 " - Parameterize behavior trees for reusability\n"
1745 " - Type-safe data access\n"
1746 " - Runtime variable inspection and debugging";
1747 HelpDialog dlg(this, "Blackboard Element - Help", helpText);
1748 dlg.ShowModal();
1749 });
1750 labelSizer1->Add(infoBtn1, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1751 labelSizer1->Add(new wxStaticText(panel, wxID_ANY, "Blackboard Element:"), 0, wxALIGN_CENTER_VERTICAL);
1752 basicSizer->Add(labelSizer1, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1753 m_blackboardElement = new wxTextCtrl(panel, wxID_ANY);
1755 basicSizer->Add(m_blackboardElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1756 row++;
1757
1758 // Blackboard ID attribute
1759 wxBoxSizer *labelSizer2 = new wxBoxSizer(wxHORIZONTAL);
1760 wxBitmapButton *infoBtn2 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1761 wxBORDER_NONE | wxBU_EXACTFIT);
1762 infoBtn2->SetBitmapHover(infoHover);
1763 infoBtn2->SetBitmapPressed(infoPressed);
1764 infoBtn2->SetBitmapDisabled(infoDisabled);
1765 infoBtn2->SetToolTip("Click for detailed information");
1766 infoBtn2->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1767 wxString helpText = "BLACKBOARD ID ATTRIBUTE\n"
1768 "===========================================================\n\n"
1769 "WHAT IS IT?\n"
1770 "The XML attribute name that contains the unique identifier for\n"
1771 "each blackboard definition.\n\n"
1772 "COMMON VALUES:\n"
1773 " - 'ID' - Standard naming (most common)\n"
1774 " - 'name' - Alternative\n"
1775 " - 'id' - Lowercase variant\n\n"
1776 "WHY IS IT NEEDED?\n"
1777 "The ID allows:\n"
1778 " - Multiple blackboard definitions in one file\n"
1779 " - Behavior trees to reference specific blackboards\n"
1780 " - Blackboard inheritance (one extends another)\n"
1781 " - Runtime blackboard lookup by name\n\n"
1782 "EXAMPLE:\n"
1783 " <BlackboardModel ID=\"BaseAI\">\n"
1784 " <Entry key=\"speed\" type=\"float\" value=\"5.0\"/>\n"
1785 " </BlackboardModel>\n\n"
1786 " <BlackboardModel ID=\"EnemyAI\" parent=\"BaseAI\">\n"
1787 " <Entry key=\"health\" type=\"int\" value=\"100\"/>\n"
1788 " <!-- Inherits 'speed' from BaseAI -->\n"
1789 " </BlackboardModel>\n\n"
1790 " <BehaviorTree ID=\"Enemy\" blackboard=\"EnemyAI\">\n"
1791 " ^^^^^^^^^^^^^^^^^^^\n"
1792 " References blackboard by ID\n"
1793 " </BehaviorTree>\n\n"
1794 "RECOMMENDATION:\n"
1795 "Use 'ID' for consistency with BehaviorTree.CPP standard.";
1796 HelpDialog dlg(this, "Blackboard ID Attribute - Help", helpText);
1797 dlg.ShowModal();
1798 });
1799 labelSizer2->Add(infoBtn2, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1800 labelSizer2->Add(new wxStaticText(panel, wxID_ANY, "Blackboard ID Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
1801 basicSizer->Add(labelSizer2, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1802 m_blackboardIdAttr = new wxTextCtrl(panel, wxID_ANY);
1804 basicSizer->Add(m_blackboardIdAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1805 row++;
1806
1807 // Parent attribute
1808 wxBoxSizer *labelSizer3 = new wxBoxSizer(wxHORIZONTAL);
1809 wxBitmapButton *infoBtn3 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1810 wxBORDER_NONE | wxBU_EXACTFIT);
1811 infoBtn3->SetBitmapHover(infoHover);
1812 infoBtn3->SetBitmapPressed(infoPressed);
1813 infoBtn3->SetBitmapDisabled(infoDisabled);
1814 infoBtn3->SetToolTip("Click for detailed information");
1815 infoBtn3->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1816 wxString helpText = "PARENT/INHERITANCE ATTRIBUTE\n"
1817 "===========================================================\n\n"
1818 "WHAT IS IT?\n"
1819 "The XML attribute name that specifies a parent blackboard from\n"
1820 "which the current blackboard inherits entries.\n\n"
1821 "COMMON VALUES:\n"
1822 " - 'parent' - Most common\n"
1823 " - 'extends' - Object-oriented terminology\n"
1824 " - 'inherits' - Explicit naming\n"
1825 " - 'base' - Alternative\n\n"
1826 "HOW INHERITANCE WORKS:\n"
1827 "A child blackboard:\n"
1828 " - Inherits ALL entries from its parent\n"
1829 " - Can add NEW entries not in the parent\n"
1830 " - Can OVERRIDE parent entries with different defaults\n"
1831 " - Cannot remove parent entries\n\n"
1832 "EXAMPLE:\n"
1833 " <BlackboardModel ID=\"BaseCharacter\">\n"
1834 " <Entry key=\"health\" type=\"int\" value=\"100\"/>\n"
1835 " <Entry key=\"speed\" type=\"float\" value=\"5.0\"/>\n"
1836 " </BlackboardModel>\n\n"
1837 " <BlackboardModel ID=\"Player\" parent=\"BaseCharacter\">\n"
1838 " <Entry key=\"mana\" type=\"int\" value=\"50\"/>\n"
1839 " <Entry key=\"speed\" type=\"float\" value=\"7.0\"/>\n"
1840 " <!-- Inherits: health=100 (from parent) -->\n"
1841 " <!-- Overrides: speed=7.0 (not 5.0) -->\n"
1842 " <!-- Adds: mana=50 (new entry) -->\n"
1843 " </BlackboardModel>\n\n"
1844 "BENEFITS:\n"
1845 " - Reduce duplication in blackboard definitions\n"
1846 " - Create base templates for common variables\n"
1847 " - Organize related blackboards hierarchically";
1848 HelpDialog dlg(this, "Parent/Inheritance Attribute - Help", helpText);
1849 dlg.ShowModal();
1850 });
1851 labelSizer3->Add(infoBtn3, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1852 labelSizer3->Add(new wxStaticText(panel, wxID_ANY, "Parent/Inheritance Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
1853 basicSizer->Add(labelSizer3, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1854 m_blackboardParentAttr = new wxTextCtrl(panel, wxID_ANY);
1856 basicSizer->Add(m_blackboardParentAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1857 row++;
1858
1859 // Entry element
1860 wxBoxSizer *labelSizer4 = new wxBoxSizer(wxHORIZONTAL);
1861 wxBitmapButton *infoBtn4 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1862 wxBORDER_NONE | wxBU_EXACTFIT);
1863 infoBtn4->SetBitmapHover(infoHover);
1864 infoBtn4->SetBitmapPressed(infoPressed);
1865 infoBtn4->SetBitmapDisabled(infoDisabled);
1866 infoBtn4->SetToolTip("Click for detailed information");
1867 infoBtn4->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1868 wxString helpText = "ENTRY ELEMENT\n"
1869 "===========================================================\n\n"
1870 "WHAT IS IT?\n"
1871 "The XML element name that represents a single variable/entry\n"
1872 "within a blackboard definition.\n\n"
1873 "COMMON VALUES:\n"
1874 " - 'Entry' - BehaviorTree.CPP standard\n"
1875 " - 'Variable' - Descriptive alternative\n"
1876 " - 'Field' - Object-oriented terminology\n"
1877 " - 'Property' - Another variant\n\n"
1878 "WHAT IS AN ENTRY?\n"
1879 "Each entry represents ONE variable in the blackboard:\n"
1880 " - Has a KEY (variable name)\n"
1881 " - Has a TYPE (int, float, string, bool, etc.)\n"
1882 " - May have a DEFAULT VALUE\n"
1883 " - Can be read/written by nodes during execution\n\n"
1884 "EXAMPLE:\n"
1885 " <BlackboardModel ID=\"GameAI\">\n"
1886 " <Entry key=\"playerHealth\" type=\"int\" value=\"100\"/>\n"
1887 " <Entry key=\"targetPosition\" type=\"Vector3\"/>\n"
1888 " <Entry key=\"isAlerted\" type=\"bool\" value=\"false\"/>\n"
1889 " <Entry key=\"patrolSpeed\" type=\"float\" value=\"2.5\"/>\n"
1890 " <Entry key=\"weaponName\" type=\"string\" value=\"Sword\"/>\n"
1891 " </BlackboardModel>\n\n"
1892 "USAGE IN BEHAVIOR TREE:\n"
1893 " <Condition name=\"CheckHealth\">\n"
1894 " <input key=\"health\" blackboard_key=\"playerHealth\"/>\n"
1895 " </Condition>\n\n"
1896 " <Action name=\"SetAlert\">\n"
1897 " <output key=\"isAlerted\" blackboard_key=\"isAlerted\" value=\"true\"/>\n"
1898 " </Action>";
1899 HelpDialog dlg(this, "Entry Element - Help", helpText);
1900 dlg.ShowModal();
1901 });
1902 labelSizer4->Add(infoBtn4, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1903 labelSizer4->Add(new wxStaticText(panel, wxID_ANY, "Entry Element:"), 0, wxALIGN_CENTER_VERTICAL);
1904 basicSizer->Add(labelSizer4, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1905 m_entryElement = new wxTextCtrl(panel, wxID_ANY);
1906 m_entryElement->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
1907 basicSizer->Add(m_entryElement, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1908 row++;
1909
1910 // Entry key attribute
1911 wxBoxSizer *labelSizer5 = new wxBoxSizer(wxHORIZONTAL);
1912 wxBitmapButton *infoBtn5 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1913 wxBORDER_NONE | wxBU_EXACTFIT);
1914 infoBtn5->SetBitmapHover(infoHover);
1915 infoBtn5->SetBitmapPressed(infoPressed);
1916 infoBtn5->SetBitmapDisabled(infoDisabled);
1917 infoBtn5->SetToolTip("Click for detailed information");
1918 infoBtn5->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1919 wxString helpText = "ENTRY KEY ATTRIBUTE\n"
1920 "===========================================================\n\n"
1921 "WHAT IS IT?\n"
1922 "The XML attribute name that contains the KEY (variable name)\n"
1923 "for a blackboard entry.\n\n"
1924 "COMMON VALUES:\n"
1925 " - 'key' - BehaviorTree.CPP standard\n"
1926 " - 'name' - Alternative common usage\n"
1927 " - 'id' - Some implementations\n\n"
1928 "WHAT IS A KEY?\n"
1929 "The key is the UNIQUE IDENTIFIER for a variable within a blackboard:\n"
1930 " - Acts as the variable name\n"
1931 " - Used to read/write the value at runtime\n"
1932 " - Must be unique within the blackboard\n"
1933 " - Case-sensitive (usually)\n\n"
1934 "EXAMPLE:\n"
1935 " <Entry key=\"playerHealth\" type=\"int\" value=\"100\"/>\n"
1936 " ^^^^^^^^^^^^^^^^^^^\n"
1937 " This is the key - the variable name\n\n"
1938 " <!-- In behavior tree nodes: -->\n"
1939 " <Condition name=\"IsHealthLow\">\n"
1940 " <input key=\"health\" blackboard_key=\"playerHealth\"/>\n"
1941 " ^^^^^^^^^^^^^^^^^\n"
1942 " References by key\n"
1943 " </Condition>\n\n"
1944 "NAMING CONVENTIONS:\n"
1945 " - camelCase: playerHealth, maxSpeed, isActive\n"
1946 " - snake_case: player_health, max_speed, is_active\n"
1947 " - PascalCase: PlayerHealth, MaxSpeed, IsActive\n\n"
1948 "RECOMMENDATION:\n"
1949 "Use descriptive, consistent names following your project's\n"
1950 "naming convention. Common standard is camelCase.";
1951 HelpDialog dlg(this, "Entry Key Attribute - Help", helpText);
1952 dlg.ShowModal();
1953 });
1954 labelSizer5->Add(infoBtn5, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
1955 labelSizer5->Add(new wxStaticText(panel, wxID_ANY, "Entry Key Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
1956 basicSizer->Add(labelSizer5, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
1957 m_entryKeyAttr = new wxTextCtrl(panel, wxID_ANY);
1958 m_entryKeyAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
1959 basicSizer->Add(m_entryKeyAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
1960 row++;
1961
1962 // Entry type attribute
1963 wxBoxSizer *labelSizer6 = new wxBoxSizer(wxHORIZONTAL);
1964 wxBitmapButton *infoBtn6 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
1965 wxBORDER_NONE | wxBU_EXACTFIT);
1966 infoBtn6->SetBitmapHover(infoHover);
1967 infoBtn6->SetBitmapPressed(infoPressed);
1968 infoBtn6->SetBitmapDisabled(infoDisabled);
1969 infoBtn6->SetToolTip("Click for detailed information");
1970 infoBtn6->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
1971 wxString helpText = "ENTRY TYPE ATTRIBUTE\n"
1972 "===========================================================\n\n"
1973 "WHAT IS IT?\n"
1974 "The XML attribute name that specifies the DATA TYPE for a\n"
1975 "blackboard entry (int, float, string, bool, etc.).\n\n"
1976 "COMMON VALUES:\n"
1977 " - 'type' - Most common standard\n"
1978 " - 'dataType' - Explicit variant\n"
1979 " - 'valueType' - Alternative\n\n"
1980 "SUPPORTED TYPES (typical):\n"
1981 " PRIMITIVES:\n"
1982 " - 'int' / 'integer' - Whole numbers (e.g., 42, -10)\n"
1983 " - 'float' / 'double' - Decimals (e.g., 3.14, -2.5)\n"
1984 " - 'bool' / 'boolean' - true/false\n"
1985 " - 'string' - Text (e.g., \"Hello\", \"weapon_1\")\n\n"
1986 " ADVANCED (depends on implementation):\n"
1987 " - 'Vector2' / 'Vector3' - Position/direction\n"
1988 " - 'Pose' / 'Pose2D' - Position + rotation\n"
1989 " - 'array' - List of values\n"
1990 " - 'object' - Complex custom types\n\n"
1991 "EXAMPLE:\n"
1992 " <Entry key=\"health\" type=\"int\" value=\"100\"/>\n"
1993 " <Entry key=\"speed\" type=\"float\" value=\"5.5\"/>\n"
1994 " <Entry key=\"isAlive\" type=\"bool\" value=\"true\"/>\n"
1995 " <Entry key=\"name\" type=\"string\" value=\"Player\"/>\n"
1996 " <Entry key=\"position\" type=\"Vector3\"/>\n\n"
1997 "WHY TYPES MATTER:\n"
1998 " - Type safety: prevent wrong value assignments\n"
1999 " - Runtime validation\n"
2000 " - Editor support (proper UI widgets)\n"
2001 " - Performance optimization";
2002 HelpDialog dlg(this, "Entry Type Attribute - Help", helpText);
2003 dlg.ShowModal();
2004 });
2005 labelSizer6->Add(infoBtn6, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2006 labelSizer6->Add(new wxStaticText(panel, wxID_ANY, "Entry Type Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
2007 basicSizer->Add(labelSizer6, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
2008 m_entryTypeAttr = new wxTextCtrl(panel, wxID_ANY);
2009 m_entryTypeAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
2010 basicSizer->Add(m_entryTypeAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
2011 row++;
2012
2013 // Entry value attribute
2014 wxBoxSizer *labelSizer7 = new wxBoxSizer(wxHORIZONTAL);
2015 wxBitmapButton *infoBtn7 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
2016 wxBORDER_NONE | wxBU_EXACTFIT);
2017 infoBtn7->SetBitmapHover(infoHover);
2018 infoBtn7->SetBitmapPressed(infoPressed);
2019 infoBtn7->SetBitmapDisabled(infoDisabled);
2020 infoBtn7->SetToolTip("Click for detailed information");
2021 infoBtn7->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2022 wxString helpText = "ENTRY VALUE ATTRIBUTE\n"
2023 "===========================================================\n\n"
2024 "WHAT IS IT?\n"
2025 "The XML attribute name that contains the DEFAULT VALUE for a\n"
2026 "blackboard entry.\n\n"
2027 "COMMON VALUES:\n"
2028 " - 'value' - Most common\n"
2029 " - 'default' - Explicit naming\n"
2030 " - 'defaultValue' - Verbose variant\n\n"
2031 "WHAT IS A DEFAULT VALUE?\n"
2032 "The default value is the INITIAL value assigned when:\n"
2033 " - A blackboard is first created/instantiated\n"
2034 " - A behavior tree starts executing\n"
2035 " - The blackboard is reset\n\n"
2036 "OPTIONAL vs REQUIRED:\n"
2037 " - Some entries HAVE defaults (value specified)\n"
2038 " - Some entries DON'T (must be set at runtime)\n\n"
2039 "EXAMPLE:\n"
2040 " <Entry key=\"health\" type=\"int\" value=\"100\"/>\n"
2041 " ^^^^^^^^^^^^^\n"
2042 " Starts with 100\n\n"
2043 " <Entry key=\"currentTarget\" type=\"string\"/>\n"
2044 " No default - must be set\n\n"
2045 " <Entry key=\"isAlerted\" type=\"bool\" value=\"false\"/>\n"
2046 " ^^^^^^^^^^^^^^^^\n"
2047 " Starts as false\n\n"
2048 "TYPE-SPECIFIC FORMATS:\n"
2049 " - int: \"42\", \"-10\", \"0\"\n"
2050 " - float: \"3.14\", \"-2.5\", \"0.0\"\n"
2051 " - bool: \"true\", \"false\" (case-insensitive)\n"
2052 " - string: \"PlayerName\", \"idle_animation\"\n"
2053 " - Vector3: \"1.0,2.0,3.0\" (comma-separated)\n\n"
2054 "RECOMMENDATION:\n"
2055 "Provide sensible defaults when possible to make trees\n"
2056 "more self-contained and easier to test.";
2057 HelpDialog dlg(this, "Entry Value Attribute - Help", helpText);
2058 dlg.ShowModal();
2059 });
2060 labelSizer7->Add(infoBtn7, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2061 labelSizer7->Add(new wxStaticText(panel, wxID_ANY, "Entry Value Attribute:"), 0, wxALIGN_CENTER_VERTICAL);
2062 basicSizer->Add(labelSizer7, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
2063 m_entryValueAttr = new wxTextCtrl(panel, wxID_ANY);
2064 m_entryValueAttr->Bind(wxEVT_TEXT, &ParserConfigDialog::OnFieldChanged, this);
2065 basicSizer->Add(m_entryValueAttr, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
2066 row++;
2067
2068 basicSizer->AddGrowableCol(1);
2069 mainSizer->Add(basicSizer, 0, wxEXPAND | wxALL, 5);
2070
2071 // Type mappings
2072 wxStaticBoxSizer *mappingsBox = new wxStaticBoxSizer(wxVERTICAL, panel, "Data Type Mappings");
2073 m_typeMappingsGrid = new wxGrid(mappingsBox->GetStaticBox(), wxID_ANY);
2074 m_typeMappingsGrid->CreateGrid(0, 2);
2075 m_typeMappingsGrid->SetColLabelValue(0, "XML Type");
2076 m_typeMappingsGrid->SetColLabelValue(1, "Internal Type");
2077
2078 // Set wider column widths to make text fully visible
2079 m_typeMappingsGrid->SetColSize(0, 250); // XML Type column width
2080 m_typeMappingsGrid->SetColSize(1, 250); // Internal Type column width
2081
2082 m_typeMappingsGrid->SetMinSize(wxSize(-1, 150));
2083 mappingsBox->Add(m_typeMappingsGrid, 1, wxEXPAND | wxALL, 5);
2084
2085 wxBoxSizer *mappingBtnSizer = new wxBoxSizer(wxHORIZONTAL);
2086 m_addMappingBtn = new wxButton(mappingsBox->GetStaticBox(), ID_ADD_MAPPING, "Add Mapping");
2087 m_removeMappingBtn = new wxButton(mappingsBox->GetStaticBox(), ID_REMOVE_MAPPING, "Remove Selected");
2088 mappingBtnSizer->Add(m_addMappingBtn, 0, wxALL, 2);
2089 mappingBtnSizer->Add(m_removeMappingBtn, 0, wxALL, 2);
2090 mappingsBox->Add(mappingBtnSizer, 0);
2091
2092 mainSizer->Add(mappingsBox, 1, wxEXPAND | wxALL, 5);
2093
2094 // Allow undefined keys
2095 m_allowUndefinedKeys = new wxCheckBox(panel, wxID_ANY, "Allow Undefined Blackboard Keys");
2096 mainSizer->Add(m_allowUndefinedKeys, 0, wxALL, 5);
2097
2098 panel->SetSizer(mainSizer);
2099 return panel;
2100}
2101
2102wxPanel *ParserConfigDialog::CreateAdvancedTab(wxNotebook *notebook) {
2103 wxPanel *panel = new wxPanel(notebook);
2104 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
2105
2106 // Load info button bitmaps
2107 wxBitmap infoNormal(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2108 wxBitmap infoHover(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2109 wxBitmap infoPressed(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2110 wxBitmap infoDisabled(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2111
2112 // Options in grid
2113 wxGridBagSizer *sizer = new wxGridBagSizer(5, 5);
2114 int row = 0;
2115
2116 // Progress reporting
2117 wxBoxSizer *labelSizer1 = new wxBoxSizer(wxHORIZONTAL);
2118 wxBitmapButton *infoBtn1 = new wxBitmapButton(panel, wxID_ANY, infoNormal, wxDefaultPosition, wxSize(40, 40),
2119 wxBORDER_NONE | wxBU_EXACTFIT);
2120 infoBtn1->SetBitmapHover(infoHover);
2121 infoBtn1->SetBitmapPressed(infoPressed);
2122 infoBtn1->SetBitmapDisabled(infoDisabled);
2123 infoBtn1->SetToolTip("Click for detailed information");
2124 infoBtn1->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2125 wxString helpText = "ENABLE PROGRESS REPORTING\n"
2126 "===========================================================\n\n"
2127 "WHAT IS IT?\n"
2128 "Controls whether the parser reports progress updates during XML\n"
2129 "parsing operations, especially useful for large files.\n\n"
2130 "ENABLED:\n"
2131 " - Parser emits progress callbacks during parsing\n"
2132 " - UI can show loading bars or progress indicators\n"
2133 " - Helps prevent \"app not responding\" appearance\n"
2134 " - Useful for files > 1000 nodes\n"
2135 " - Small performance overhead (~2-5%)\n\n"
2136 "DISABLED:\n"
2137 " - No progress updates\n"
2138 " - Slightly faster parsing\n"
2139 " - Simpler implementation\n"
2140 " - Best for small/medium files\n\n"
2141 "USE CASES:\n"
2142 "Enable when:\n"
2143 " - Loading very large behavior tree files\n"
2144 " - Need to show user feedback during loading\n"
2145 " - Parsing operations take > 1 second\n\n"
2146 "Disable when:\n"
2147 " - Working with small files (< 100 nodes)\n"
2148 " - Maximum parsing speed is priority\n"
2149 " - No UI for displaying progress\n\n"
2150 "RECOMMENDATION:\n"
2151 "Enable for production applications to improve user experience\n"
2152 "when loading large behavior tree files.";
2153 HelpDialog dlg(this, "Progress Reporting - Help", helpText);
2154 dlg.ShowModal();
2155 });
2156 labelSizer1->Add(infoBtn1, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2157 m_enableProgressReporting = new wxCheckBox(panel, wxID_ANY, "Enable Progress Reporting");
2158 m_enableProgressReporting->SetToolTip("Report parsing progress for large files");
2159 labelSizer1->Add(m_enableProgressReporting, 0, wxALIGN_CENTER_VERTICAL);
2160 sizer->Add(labelSizer1, wxGBPosition(row, 0), wxGBSpan(1, 2));
2161 row++;
2162
2163 // Detailed errors
2164 wxBoxSizer *labelSizer2 = new wxBoxSizer(wxHORIZONTAL);
2165 wxBitmap infoNormal2(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2166 wxBitmap infoHover2(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2167 wxBitmap infoPressed2(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2168 wxBitmap infoDisabled2(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2169 wxBitmapButton *infoBtn2 = new wxBitmapButton(panel, wxID_ANY, infoNormal2, wxDefaultPosition, wxSize(40, 40),
2170 wxBORDER_NONE | wxBU_EXACTFIT);
2171 infoBtn2->SetBitmapHover(infoHover2);
2172 infoBtn2->SetBitmapPressed(infoPressed2);
2173 infoBtn2->SetBitmapDisabled(infoDisabled2);
2174 infoBtn2->SetToolTip("Click for detailed information");
2175 infoBtn2->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2176 wxString helpText = "DETAILED ERROR MESSAGES\n"
2177 "===========================================================\n\n"
2178 "Controls the verbosity of error messages when parsing fails.\n\n"
2179 "ENABLED (Recommended for Development):\n"
2180 " - Full error descriptions with context\n"
2181 " - Shows exact line and column numbers\n"
2182 " - Includes surrounding XML context\n"
2183 " - Lists all violated validation rules\n"
2184 " - Suggests possible fixes\n\n"
2185 "Example detailed error:\n"
2186 " ERROR at line 42, column 15:\n"
2187 " Unknown element 'Sequenc' found.\n"
2188 " Context: <Sequenc name=\"Patrol\">\n"
2189 " Did you mean: 'Sequence'?\n"
2190 " Valid elements: Sequence, Selector, Parallel\n\n"
2191 "DISABLED (Production):\n"
2192 " - Brief error messages\n"
2193 " - Just error type and location\n"
2194 " - Less information leakage\n"
2195 " - Slightly faster\n\n"
2196 "Example simple error:\n"
2197 " Parse error at line 42: Unknown element\n\n"
2198 "USE CASES:\n"
2199 "Enable when:\n"
2200 " - Developing behavior trees\n"
2201 " - Debugging XML format issues\n"
2202 " - Users need helpful error messages\n\n"
2203 "Disable when:\n"
2204 " - Production/release builds\n"
2205 " - Security concerns about information disclosure\n"
2206 " - Minimal error output desired\n\n"
2207 "RECOMMENDATION:\n"
2208 "Keep ENABLED during development. Consider disabling for\n"
2209 "production if error details are security-sensitive.";
2210 HelpDialog dlg(this, "Detailed Error Messages - Help", helpText);
2211 dlg.ShowModal();
2212 });
2213 labelSizer2->Add(infoBtn2, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2214 m_detailedErrors = new wxCheckBox(panel, wxID_ANY, "Detailed Error Messages");
2215 m_detailedErrors->SetToolTip("Include detailed context in error messages");
2216 labelSizer2->Add(m_detailedErrors, 0, wxALIGN_CENTER_VERTICAL);
2217 sizer->Add(labelSizer2, wxGBPosition(row, 0), wxGBSpan(1, 2));
2218 row++;
2219
2220 // Track line/column
2221 wxBoxSizer *labelSizer3 = new wxBoxSizer(wxHORIZONTAL);
2222 wxBitmap infoNormal3(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2223 wxBitmap infoHover3(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2224 wxBitmap infoPressed3(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2225 wxBitmap infoDisabled3(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2226 wxBitmapButton *infoBtn3 = new wxBitmapButton(panel, wxID_ANY, infoNormal3, wxDefaultPosition, wxSize(40, 40),
2227 wxBORDER_NONE | wxBU_EXACTFIT);
2228 infoBtn3->SetBitmapHover(infoHover3);
2229 infoBtn3->SetBitmapPressed(infoPressed3);
2230 infoBtn3->SetBitmapDisabled(infoDisabled3);
2231 infoBtn3->SetToolTip("Click for detailed information");
2232 infoBtn3->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2233 wxString helpText = "TRACK LINE/COLUMN NUMBERS\n"
2234 "===========================================================\n\n"
2235 "Controls whether the parser maintains line and column position\n"
2236 "information for every parsed element.\n\n"
2237 "ENABLED:\n"
2238 " - Every node tracks its source location (line, column)\n"
2239 " - Error messages show EXACT locations\n"
2240 " - Enables \"jump to source\" in editors\n"
2241 " - Useful for debugging and validation\n"
2242 " - Small memory overhead per node (~8 bytes)\n"
2243 " - Minor performance impact (~5-10%)\n\n"
2244 "DISABLED:\n"
2245 " - No line/column tracking\n"
2246 " - Error messages less specific\n"
2247 " - Lower memory usage\n"
2248 " - Slightly faster parsing\n\n"
2249 "ERROR COMPARISON:\n\n"
2250 "With tracking enabled:\n"
2251 " ERROR at line 127, column 23:\n"
2252 " Invalid attribute 'tpye' on Action node.\n"
2253 " <Action tpye=\"Move\" target=\"enemy\"/>\n"
2254 " ^\n\n"
2255 "With tracking disabled:\n"
2256 " ERROR: Invalid attribute 'tpye' on Action node.\n"
2257 " (no location information available)\n\n"
2258 "USE CASES:\n"
2259 "Enable when:\n"
2260 " - Developing/debugging trees\n"
2261 " - Need precise error locations\n"
2262 " - Integrating with XML editors\n\n"
2263 "Disable when:\n"
2264 " - Memory is critical constraint\n"
2265 " - Maximum performance needed\n"
2266 " - Source locations not needed\n\n"
2267 "RECOMMENDATION:\n"
2268 "Keep ENABLED during development for better debugging.\n"
2269 "Consider disabling in memory-constrained environments.";
2270 HelpDialog dlg(this, "Track Line/Column Numbers - Help", helpText);
2271 dlg.ShowModal();
2272 });
2273 labelSizer3->Add(infoBtn3, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2274 m_trackLineColumn = new wxCheckBox(panel, wxID_ANY, "Track Line/Column Numbers");
2275 m_trackLineColumn->SetToolTip("Store source location for every element");
2276 labelSizer3->Add(m_trackLineColumn, 0, wxALIGN_CENTER_VERTICAL);
2277 sizer->Add(labelSizer3, wxGBPosition(row, 0), wxGBSpan(1, 2));
2278 row++;
2279
2280 // SubTree expansion
2281 wxBoxSizer *labelSizer4 = new wxBoxSizer(wxHORIZONTAL);
2282 wxBitmap infoNormal4(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2283 wxBitmap infoHover4(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2284 wxBitmap infoPressed4(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2285 wxBitmap infoDisabled4(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2286 wxBitmapButton *infoBtn4 = new wxBitmapButton(panel, wxID_ANY, infoNormal4, wxDefaultPosition, wxSize(40, 40),
2287 wxBORDER_NONE | wxBU_EXACTFIT);
2288 infoBtn4->SetBitmapHover(infoHover4);
2289 infoBtn4->SetBitmapPressed(infoPressed4);
2290 infoBtn4->SetBitmapDisabled(infoDisabled4);
2291 infoBtn4->SetToolTip("Click for detailed information");
2292 infoBtn4->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2293 wxString helpText = "SUBTREE EXPANSION\n"
2294 "===========================================================\n\n"
2295 "Controls WHEN subtree references are resolved and loaded during\n"
2296 "parsing. Affects memory usage and loading performance.\n\n"
2297 "THREE STRATEGIES:\n\n"
2298 "1. IMMEDIATE (Default - Recommended)\n"
2299 " - All subtrees loaded and expanded during initial parse\n"
2300 " - Complete tree structure available immediately\n"
2301 " - All errors detected at load time\n"
2302 " - Higher initial memory usage\n"
2303 " - Slightly longer initial load time\n"
2304 " Pros: Simple, predictable, catches all errors early\n"
2305 " Cons: Loads trees that may never be used\n\n"
2306 "2. LAZY (On-Demand Loading)\n"
2307 " - Subtrees loaded only when first accessed\n"
2308 " - Lower initial memory footprint\n"
2309 " - Faster initial load time\n"
2310 " - Subtree errors discovered at runtime\n"
2311 " - Small performance hit on first access\n"
2312 " Pros: Efficient for large tree collections\n"
2313 " Cons: Errors may appear during gameplay/execution\n\n"
2314 "3. MANUAL (Explicit Control)\n"
2315 " - No automatic expansion\n"
2316 " - Application controls when to load\n"
2317 " - Maximum flexibility\n"
2318 " - Requires explicit API calls\n"
2319 " Pros: Full control over loading\n"
2320 " Cons: More complex application code\n\n"
2321 "EXAMPLE:\n"
2322 "Tree with subtrees: Main -> Patrol -> GuardPoint\n\n"
2323 "IMMEDIATE: Main, Patrol, GuardPoint all loaded at once\n"
2324 "LAZY: Only Main loaded, Patrol/GuardPoint on first use\n"
2325 "MANUAL: Only Main loaded, app calls LoadSubtree() explicitly\n\n"
2326 "RECOMMENDATION:\n"
2327 "Use IMMEDIATE for most cases - simple and catches errors early.\n"
2328 "Use LAZY for very large tree collections (100+ trees).";
2329 HelpDialog dlg(this, "SubTree Expansion - Help", helpText);
2330 dlg.ShowModal();
2331 });
2332 labelSizer4->Add(infoBtn4, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2333 labelSizer4->Add(new wxStaticText(panel, wxID_ANY, "SubTree Expansion:"), 0, wxALIGN_CENTER_VERTICAL);
2334 sizer->Add(labelSizer4, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
2335 wxArrayString expansionChoices;
2336 expansionChoices.Add("Immediate");
2337 expansionChoices.Add("Lazy");
2338 expansionChoices.Add("Manual");
2339 m_subtreeExpansion = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, expansionChoices);
2340 m_subtreeExpansion->SetSelection(0);
2341 m_subtreeExpansion->SetToolTip("When to load and expand subtree references");
2342 sizer->Add(m_subtreeExpansion, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
2343 row++;
2344
2345 // Max recursion depth
2346 wxBoxSizer *labelSizer5 = new wxBoxSizer(wxHORIZONTAL);
2347 wxBitmap infoNormal5(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2348 wxBitmap infoHover5(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2349 wxBitmap infoPressed5(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2350 wxBitmap infoDisabled5(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2351 wxBitmapButton *infoBtn5 = new wxBitmapButton(panel, wxID_ANY, infoNormal5, wxDefaultPosition, wxSize(40, 40),
2352 wxBORDER_NONE | wxBU_EXACTFIT);
2353 infoBtn5->SetBitmapHover(infoHover5);
2354 infoBtn5->SetBitmapPressed(infoPressed5);
2355 infoBtn5->SetBitmapDisabled(infoDisabled5);
2356 infoBtn5->SetToolTip("Click for detailed information");
2357 infoBtn5->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2358 wxString helpText = "MAX RECURSION DEPTH\n"
2359 "===========================================================\n\n"
2360 "Maximum depth of nested subtree references allowed. Prevents\n"
2361 "infinite recursion from circular subtree references.\n\n"
2362 "WHAT IS RECURSION DEPTH?\n"
2363 "The number of levels of subtree-within-subtree nesting:\n\n"
2364 "Depth 1: MainTree -> SubTreeA (1 level)\n"
2365 "Depth 2: MainTree -> SubTreeA -> SubTreeB (2 levels)\n"
2366 "Depth 3: MainTree -> SubTreeA -> SubTreeB -> SubTreeC\n\n"
2367 "CIRCULAR REFERENCE PROTECTION:\n"
2368 "This limit prevents infinite loops from circular references:\n"
2369 " TreeA references TreeB\n"
2370 " TreeB references TreeA <- CIRCULAR!\n"
2371 "Without limit: Infinite expansion and stack overflow\n"
2372 "With limit: Stops after N levels with clear error\n\n"
2373 "VALUE GUIDELINES:\n\n"
2374 "Very Low (1-10):\n"
2375 " - Simple trees with few subtree levels\n"
2376 " - Strict control over complexity\n"
2377 " - Fast circular reference detection\n\n"
2378 "Medium (10-100): RECOMMENDED\n"
2379 " - Handles most reasonable tree structures\n"
2380 " - Default value: 100\n"
2381 " - Good balance of safety and flexibility\n\n"
2382 "High (100-1000):\n"
2383 " - Very complex nested structures\n"
2384 " - Larger memory usage\n"
2385 " - Slower to detect circular refs\n\n"
2386 "EXAMPLE ERROR:\n"
2387 "Max depth = 10, actual nesting = 12:\n"
2388 " ERROR: Max recursion depth (10) exceeded.\n"
2389 " Subtree expansion stopped at: TreeA -> TreeB -> ... (10 levels)\n"
2390 " Possible circular reference detected.\n\n"
2391 "RECOMMENDATION:\n"
2392 "Keep default 100 - catches circular refs while allowing\n"
2393 "complex legitimate nesting. Increase only if you have\n"
2394 "verified deeply nested structures.";
2395 HelpDialog dlg(this, "Max Recursion Depth - Help", helpText);
2396 dlg.ShowModal();
2397 });
2398 labelSizer5->Add(infoBtn5, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2399 labelSizer5->Add(new wxStaticText(panel, wxID_ANY, "Max Recursion Depth:"), 0, wxALIGN_CENTER_VERTICAL);
2400 sizer->Add(labelSizer5, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
2401 m_maxRecursionDepth = new wxSpinCtrl(panel, wxID_ANY);
2402 m_maxRecursionDepth->SetRange(1, 1000);
2403 m_maxRecursionDepth->SetValue(100);
2404 m_maxRecursionDepth->SetToolTip("Prevent infinite loops from circular subtree references");
2405 sizer->Add(m_maxRecursionDepth, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
2406 row++;
2407
2408 // XML namespace
2409 wxBoxSizer *labelSizer8 = new wxBoxSizer(wxHORIZONTAL);
2410 wxBitmap infoNormal8(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2411 wxBitmap infoHover8(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2412 wxBitmap infoPressed8(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2413 wxBitmap infoDisabled8(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2414 wxBitmapButton *infoBtn8 = new wxBitmapButton(panel, wxID_ANY, infoNormal8, wxDefaultPosition, wxSize(40, 40),
2415 wxBORDER_NONE | wxBU_EXACTFIT);
2416 infoBtn8->SetBitmapHover(infoHover8);
2417 infoBtn8->SetBitmapPressed(infoPressed8);
2418 infoBtn8->SetBitmapDisabled(infoDisabled8);
2419 infoBtn8->SetToolTip("Click for detailed information");
2420 infoBtn8->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2421 wxString helpText = "XML NAMESPACE URI\n"
2422 "===========================================================\n\n"
2423 "Optional XML namespace for behavior tree elements.\n"
2424 "Used for XML schema validation and element disambiguation.\n\n"
2425 "WHAT IS AN XML NAMESPACE?\n"
2426 "A unique identifier (usually a URI) that prevents naming\n"
2427 "conflicts between XML vocabularies.\n\n"
2428 "EXAMPLE WITHOUT NAMESPACE:\n"
2429 " <root>\n"
2430 " <BehaviorTree ID=\"AI\">\n"
2431 " <Sequence>...</Sequence>\n"
2432 " </BehaviorTree>\n"
2433 " </root>\n\n"
2434 "EXAMPLE WITH NAMESPACE:\n"
2435 " <root xmlns:bt=\"http://behaviortree.dev/bt/1.0\">\n"
2436 " <bt:BehaviorTree ID=\"AI\">\n"
2437 " <bt:Sequence>...</bt:Sequence>\n"
2438 " </bt:BehaviorTree>\n"
2439 " </root>\n\n"
2440 "WHEN TO USE:\n\n"
2441 "Leave Empty (Most Common):\n"
2442 " - Simple XML files\n"
2443 " - No naming conflicts\n"
2444 " - Cleaner XML\n"
2445 " - Easier to read/write\n\n"
2446 "Specify Namespace When:\n"
2447 " - Mixing multiple XML vocabularies\n"
2448 " - Need formal XML schema validation\n"
2449 " - Working with XML tools that require it\n"
2450 " - Preventing element name collisions\n\n"
2451 "COMMON NAMESPACE FORMATS:\n"
2452 " - http://behaviortree.dev/bt/1.0\n"
2453 " - urn:behaviortree:schema:1.0\n"
2454 " - http://example.com/schemas/bt\n\n"
2455 "Note: The URI doesn't need to be a real web address.\n"
2456 "It's just a unique identifier.\n\n"
2457 "IMPACT:\n"
2458 "If specified, all behavior tree elements must use the\n"
2459 "namespace prefix in your XML files.\n\n"
2460 "RECOMMENDATION:\n"
2461 "Leave empty unless you have specific requirements for\n"
2462 "namespace-based XML validation or element disambiguation.";
2463 HelpDialog dlg(this, "XML Namespace - Help", helpText);
2464 dlg.ShowModal();
2465 });
2466 labelSizer8->Add(infoBtn8, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2467 labelSizer8->Add(new wxStaticText(panel, wxID_ANY, "XML Namespace URI:"), 0, wxALIGN_CENTER_VERTICAL);
2468 sizer->Add(labelSizer8, wxGBPosition(row, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
2469 m_xmlNamespace = new wxTextCtrl(panel, wxID_ANY);
2470 m_xmlNamespace->SetToolTip("Optional namespace URI (e.g., http://behaviortree.dev/bt/1.0)");
2471 sizer->Add(m_xmlNamespace, wxGBPosition(row, 1), wxDefaultSpan, wxEXPAND);
2472 row++;
2473
2474 // Preserve comments
2475 wxBoxSizer *labelSizer9 = new wxBoxSizer(wxHORIZONTAL);
2476 wxBitmap infoNormal9(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2477 wxBitmap infoHover9(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2478 wxBitmap infoPressed9(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2479 wxBitmap infoDisabled9(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2480 wxBitmapButton *infoBtn9 = new wxBitmapButton(panel, wxID_ANY, infoNormal9, wxDefaultPosition, wxSize(40, 40),
2481 wxBORDER_NONE | wxBU_EXACTFIT);
2482 infoBtn9->SetBitmapHover(infoHover9);
2483 infoBtn9->SetBitmapPressed(infoPressed9);
2484 infoBtn9->SetBitmapDisabled(infoDisabled9);
2485 infoBtn9->SetToolTip("Click for detailed information");
2486 infoBtn9->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2487 wxString helpText = "PRESERVE XML COMMENTS\n"
2488 "===========================================================\n\n"
2489 "Controls whether XML comments are kept in memory after parsing.\n\n"
2490 "ENABLED:\n"
2491 " - Comments stored in parsed tree structure\n"
2492 " - Can be accessed programmatically\n"
2493 " - Preserved when saving/exporting trees\n"
2494 " - Useful for round-trip editing\n"
2495 " - Small memory overhead\n\n"
2496 "DISABLED (Default):\n"
2497 " - Comments discarded during parsing\n"
2498 " - Lower memory usage\n"
2499 " - Slightly faster parsing\n"
2500 " - Lost if tree is saved back to XML\n\n"
2501 "XML COMMENTS:\n"
2502 " <!-- This is a comment -->\n"
2503 " <!-- Comments can span\n"
2504 " multiple lines -->\n\n"
2505 "EXAMPLE:\n"
2506 " <BehaviorTree ID=\"AI\">\n"
2507 " <!-- This is the patrol sequence -->\n"
2508 " <Sequence>\n"
2509 " <Action name=\"Move\"/> <!-- Move forward -->\n"
2510 " </Sequence>\n"
2511 " </BehaviorTree>\n\n"
2512 "USE CASES:\n\n"
2513 "Enable when:\n"
2514 " - Building XML editor/viewer\n"
2515 " - Need to preserve documentation\n"
2516 " - Round-trip editing (load -> modify -> save)\n"
2517 " - Comments contain important metadata\n\n"
2518 "Disable when:\n"
2519 " - Only loading trees for execution\n"
2520 " - Memory is constrained\n"
2521 " - Comments are just temporary notes\n"
2522 " - Maximum parsing speed needed\n\n"
2523 "RECOMMENDATION:\n"
2524 "Enable if building an editor or if comments contain important\n"
2525 "information. Disable for runtime-only tree loading.";
2526 HelpDialog dlg(this, "Preserve Comments - Help", helpText);
2527 dlg.ShowModal();
2528 });
2529 labelSizer9->Add(infoBtn9, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2530 m_preserveComments = new wxCheckBox(panel, wxID_ANY, "Preserve XML Comments");
2531 m_preserveComments->SetToolTip("Keep comments in memory for round-trip editing");
2532 labelSizer9->Add(m_preserveComments, 0, wxALIGN_CENTER_VERTICAL);
2533 sizer->Add(labelSizer9, wxGBPosition(row, 0), wxGBSpan(1, 2));
2534 row++;
2535
2536 // Preserve attribute order
2537 wxBoxSizer *labelSizer10 = new wxBoxSizer(wxHORIZONTAL);
2538 wxBitmap infoNormal10(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2539 wxBitmap infoHover10(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2540 wxBitmap infoPressed10(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2541 wxBitmap infoDisabled10(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2542 wxBitmapButton *infoBtn10 = new wxBitmapButton(panel, wxID_ANY, infoNormal10, wxDefaultPosition, wxSize(40, 40),
2543 wxBORDER_NONE | wxBU_EXACTFIT);
2544 infoBtn10->SetBitmapHover(infoHover10);
2545 infoBtn10->SetBitmapPressed(infoPressed10);
2546 infoBtn10->SetBitmapDisabled(infoDisabled10);
2547 infoBtn10->SetToolTip("Click for detailed information");
2548 infoBtn10->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2549 wxString helpText = "PRESERVE ATTRIBUTE ORDER\n"
2550 "===========================================================\n\n"
2551 "Controls whether the order of XML attributes is maintained\n"
2552 "exactly as they appear in the source file.\n\n"
2553 "ENABLED:\n"
2554 " - Attributes stored in original order\n"
2555 " - When saved, attributes appear in same order\n"
2556 " - Useful for version control (minimal diffs)\n"
2557 " - Slight memory/performance overhead\n"
2558 " - Better for human-edited XML\n\n"
2559 "DISABLED (Default):\n"
2560 " - Attributes may be reordered internally\n"
2561 " - Typically alphabetical order\n"
2562 " - Slightly more efficient\n"
2563 " - Doesn't affect functionality\n"
2564 " - Standard XML behavior\n\n"
2565 "XML ATTRIBUTE ORDER:\n\n"
2566 "Original XML:\n"
2567 " <Action name=\"Move\" speed=\"5.0\" target=\"enemy\"/>\n\n"
2568 "Preserved (enabled):\n"
2569 " <Action name=\"Move\" speed=\"5.0\" target=\"enemy\"/>\n"
2570 " (Exact same order)\n\n"
2571 "Not preserved (disabled):\n"
2572 " <Action name=\"Move\" speed=\"5.0\" target=\"enemy\"/>\n"
2573 " (May become: name, speed, target OR alphabetically)\n\n"
2574 "IMPORTANT NOTE:\n"
2575 "XML specification says attribute order is NOT significant.\n"
2576 "All XML parsers must work regardless of attribute order.\n\n"
2577 "USE CASES:\n\n"
2578 "Enable when:\n"
2579 " - Using version control (Git, SVN)\n"
2580 " - Want minimal diffs between versions\n"
2581 " - Hand-editing XML files\n"
2582 " - Order has semantic meaning to humans\n\n"
2583 "Disable when:\n"
2584 " - Pure runtime loading\n"
2585 " - No round-trip editing\n"
2586 " - Programmatically generated XML\n"
2587 " - Order doesn't matter\n\n"
2588 "RECOMMENDATION:\n"
2589 "Enable for editor applications and version-controlled files.\n"
2590 "Disable for runtime-only loading.";
2591 HelpDialog dlg(this, "Preserve Attribute Order - Help", helpText);
2592 dlg.ShowModal();
2593 });
2594 labelSizer10->Add(infoBtn10, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2595 m_preserveAttrOrder = new wxCheckBox(panel, wxID_ANY, "Preserve Attribute Order");
2596 m_preserveAttrOrder->SetToolTip("Maintain original attribute order for version control");
2597 labelSizer10->Add(m_preserveAttrOrder, 0, wxALIGN_CENTER_VERTICAL);
2598 sizer->Add(labelSizer10, wxGBPosition(row, 0), wxGBSpan(1, 2));
2599 row++;
2600
2601 sizer->AddGrowableCol(1);
2602 mainSizer->Add(sizer, 0, wxEXPAND | wxALL, 5);
2603
2604 // Add separator line
2605 mainSizer->Add(new wxStaticLine(panel), 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5);
2606
2607 // Custom validation rules section with toggle
2608 // Toggle checkbox for enabling/disabling custom validation
2609 m_enableCustomValidation = new wxCheckBox(panel, ID_TOGGLE_CUSTOM_VALIDATION, "Enable Custom Validation Rules");
2610 m_enableCustomValidation->SetValue(false); // Disabled by default
2611 m_enableCustomValidation->SetToolTip("Toggle custom validation rules on/off");
2612 mainSizer->Add(m_enableCustomValidation, 0, wxALL, 5);
2613
2614 // Custom validation rules (multiline)
2615 wxBoxSizer *rulesLabelSizer = new wxBoxSizer(wxHORIZONTAL);
2616 wxBitmap infoNormal11(GetResourcePath("buttons/info/Info_Normal_20.png"), wxBITMAP_TYPE_PNG);
2617 wxBitmap infoHover11(GetResourcePath("buttons/info/Info_Hovered_20.png"), wxBITMAP_TYPE_PNG);
2618 wxBitmap infoPressed11(GetResourcePath("buttons/info/Info_Pressed_20.png"), wxBITMAP_TYPE_PNG);
2619 wxBitmap infoDisabled11(GetResourcePath("buttons/info/Info_Disabled_20.png"), wxBITMAP_TYPE_PNG);
2620 wxBitmapButton *infoBtn11 = new wxBitmapButton(panel, wxID_ANY, infoNormal11, wxDefaultPosition, wxSize(40, 40),
2621 wxBORDER_NONE | wxBU_EXACTFIT);
2622 infoBtn11->SetBitmapHover(infoHover11);
2623 infoBtn11->SetBitmapPressed(infoPressed11);
2624 infoBtn11->SetBitmapDisabled(infoDisabled11);
2625 infoBtn11->SetToolTip("Click for detailed information");
2626 infoBtn11->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
2627 wxString helpText = "CUSTOM VALIDATION RULES\n"
2628 "===========================================================\n\n"
2629 "Define custom validation logic beyond standard XML schema validation.\n"
2630 "Specify rules using a simple scripting language or expressions.\n\n"
2631 "WHAT ARE VALIDATION RULES?\n"
2632 "Custom constraints that your behavior trees must satisfy:\n"
2633 " - Structural rules (e.g., Sequence must have 2+ children)\n"
2634 " - Naming conventions (e.g., Actions must start with verb)\n"
2635 " - Business logic (e.g., no more than 3 decorators deep)\n"
2636 " - Cross-tree references (e.g., all subtrees must exist)\n\n"
2637 "RULE FORMAT:\n"
2638 "One rule per line. Each rule has:\n"
2639 " - Condition: When to check\n"
2640 " - Validation: What to verify\n"
2641 " - Message: Error to show if fails\n\n"
2642 "EXAMPLE RULES:\n\n"
2643 "1. Structural Rule:\n"
2644 " node.type == 'Sequence' && node.children.count < 2:\n"
2645 " 'Sequence nodes must have at least 2 children'\n\n"
2646 "2. Naming Convention:\n"
2647 " node.type == 'Action' && !node.name.startsWithVerb():\n"
2648 " 'Action names must start with a verb'\n\n"
2649 "3. Decorator Depth:\n"
2650 " node.type == 'Decorator' && node.depth > 3:\n"
2651 " 'Decorators cannot be nested more than 3 levels'\n\n"
2652 "4. Required Attributes:\n"
2653 " node.type == 'Action' && !node.hasAttribute('timeout'):\n"
2654 " 'All actions must specify a timeout'\n\n"
2655 "5. Subtree References:\n"
2656 " node.type == 'SubTree' && !subtreeExists(node.ID):\n"
2657 " 'Referenced subtree does not exist'\n\n"
2658 "AVAILABLE PROPERTIES:\n"
2659 " - node.type: Node type (Action, Sequence, etc.)\n"
2660 " - node.name: Node name/ID\n"
2661 " - node.children.count: Number of child nodes\n"
2662 " - node.depth: Nesting level\n"
2663 " - node.attributes: Map of attributes\n"
2664 " - tree.ID: Current tree identifier\n\n"
2665 "WHEN VALIDATION RUNS:\n"
2666 " - After parsing completes\n"
2667 " - Before tree is made available to application\n"
2668 " - All rules checked, multiple errors reported\n\n"
2669 "ERROR HANDLING:\n"
2670 "If any rule fails:\n"
2671 " - Clear error message shown\n"
2672 " - Parse operation fails\n"
2673 " - Tree not loaded into memory\n\n"
2674 "PERFORMANCE:\n"
2675 " - Rules checked once at parse time\n"
2676 " - No runtime overhead\n"
2677 " - Complex rules may slow initial load\n\n"
2678 "RECOMMENDATION:\n"
2679 "Use for project-specific constraints that can't be expressed\n"
2680 "in XML schema. Keep rules simple and clear. Too many complex\n"
2681 "rules can make debugging difficult.";
2682 HelpDialog dlg(this, "Custom Validation Rules - Help", helpText);
2683 dlg.ShowModal();
2684 });
2685 rulesLabelSizer->Add(infoBtn11, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
2686 rulesLabelSizer->Add(new wxStaticText(panel, wxID_ANY, "Custom Validation Rules: ( experimental )"), 0,
2687 wxALIGN_CENTER_VERTICAL);
2688 mainSizer->Add(rulesLabelSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5);
2689 m_customValidation = new wxTextCtrl(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
2690 m_customValidation->SetMinSize(wxSize(-1, 100));
2691 m_customValidation->SetToolTip("Define custom validation expressions (one rule per line)");
2692 m_customValidation->Enable(false); // Disabled by default (matches checkbox state)
2693 mainSizer->Add(m_customValidation, 1, wxEXPAND | wxALL, 5);
2694
2695 panel->SetSizer(mainSizer);
2696 return panel;
2697}
2698
2699wxPanel *ParserConfigDialog::CreateBottomPanel(wxWindow *parent) {
2700 wxPanel *panel = new wxPanel(parent);
2701 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
2702
2703 // Profile metadata
2704 wxGridBagSizer *metaSizer = new wxGridBagSizer(5, 5);
2705
2706 metaSizer->Add(new wxStaticText(panel, wxID_ANY, "Profile Name:"), wxGBPosition(0, 0), wxDefaultSpan,
2707 wxALIGN_CENTER_VERTICAL);
2708 m_profileName = new wxTextCtrl(panel, wxID_ANY);
2709 metaSizer->Add(m_profileName, wxGBPosition(0, 1), wxDefaultSpan, wxEXPAND);
2710
2711 metaSizer->Add(new wxStaticText(panel, wxID_ANY, "Description:"), wxGBPosition(1, 0), wxDefaultSpan, wxALIGN_TOP);
2712 m_profileDescription = new wxTextCtrl(panel, wxID_ANY, "", wxDefaultPosition, wxSize(-1, 60), wxTE_MULTILINE);
2713 metaSizer->Add(m_profileDescription, wxGBPosition(1, 1), wxDefaultSpan, wxEXPAND);
2714
2715 metaSizer->AddGrowableCol(1);
2716 mainSizer->Add(metaSizer, 0, wxEXPAND | wxALL, 5);
2717
2718 // Buttons
2719 wxBoxSizer *btnSizer = new wxBoxSizer(wxHORIZONTAL);
2720
2721 m_testValidateBtn = new wxButton(panel, ID_TEST_VALIDATE, "Test/Validate");
2722 btnSizer->Add(m_testValidateBtn, 0, wxALL, 2);
2723
2724 m_previewJSONBtn = new wxButton(panel, ID_PREVIEW_JSON, "Preview JSON");
2725 btnSizer->Add(m_previewJSONBtn, 0, wxALL, 2);
2726
2727 m_resetDefaultsBtn = new wxButton(panel, ID_RESET_DEFAULTS, "Reset to Defaults");
2728 btnSizer->Add(m_resetDefaultsBtn, 0, wxALL, 2);
2729
2730 btnSizer->AddStretchSpacer();
2731
2732 m_saveBtn = new wxButton(panel, ID_SAVE, "Save");
2733 btnSizer->Add(m_saveBtn, 0, wxALL, 2);
2734
2735 m_saveAsBtn = new wxButton(panel, ID_SAVE_AS, "Save As...");
2736 btnSizer->Add(m_saveAsBtn, 0, wxALL, 2);
2737
2738 m_cancelBtn = new wxButton(panel, wxID_CANCEL, "Cancel");
2739 btnSizer->Add(m_cancelBtn, 0, wxALL, 2);
2740
2741 mainSizer->Add(btnSizer, 0, wxEXPAND | wxALL, 5);
2742
2743 panel->SetSizer(mainSizer);
2744 return panel;
2745}
2746
2747// This file is getting too long. I'll continue with event handlers in the next part...
2748// For now, let me add placeholder implementations
2749
2751 auto &configMgr = EmberCore::ConfigManager::GetInstance();
2752 auto profileNames = configMgr.GetProfileNames();
2753 wxString activeName(configMgr.GetActiveProfileName());
2754
2755 m_profileList->DeleteAllItems();
2756
2757 long activeIndex = -1;
2758 for (size_t i = 0; i < profileNames.size(); ++i) {
2759 const auto &name = profileNames[i];
2760
2761 // Add visual marker (*) for active profile and color it green
2762 wxString displayName = name;
2763 bool isActive = (name == activeName);
2764
2765 if (isActive) {
2766 displayName = wxString::FromUTF8("* ") + name; // Safe Unicode handling
2767 activeIndex = i;
2768 }
2769
2770 // Insert item into list
2771 long index = m_profileList->InsertItem(i, displayName);
2772
2773 // Set color: green for active, white for others
2774 if (isActive) {
2775 m_profileList->SetItemTextColour(index, wxColour(46, 125, 50)); // Dark green
2776 } else {
2777 m_profileList->SetItemTextColour(index, *wxWHITE); // White for inactive
2778 }
2779 }
2780
2781 // Select and load active profile
2782 if (activeIndex != -1) {
2783 m_profileList->SetItemState(activeIndex, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
2784 m_profileList->EnsureVisible(activeIndex);
2785 auto profile = configMgr.GetProfile(activeName.ToStdString());
2786 LoadProfileData(profile);
2787 }
2788}
2789
2790void ParserConfigDialog::LoadProfileData(std::shared_ptr<EmberCore::ParserProfile> profile) {
2791 if (!profile)
2792 return;
2793
2794 m_currentProfile = profile;
2795 m_profileName->SetValue(profile->GetName());
2796 m_profileDescription->SetValue(profile->GetDescription());
2797
2798 SetUIFromConfig(profile->GetConfig());
2799 m_modified = false;
2800}
2801
2803 // Tab 1: Document Structure
2804 const auto &docConfig = config.GetDocumentConfig();
2805 m_rootElement->SetValue(docConfig.root_element);
2806 m_mainTreeAttr->SetValue(docConfig.main_tree_attribute);
2807 m_caseSensitive->SetValue(docConfig.case_sensitive);
2808 m_allowUnknownElements->SetValue(docConfig.allow_unknown_elements);
2809 m_whitespaceHandling->SetSelection(static_cast<int>(docConfig.whitespace_handling));
2810
2811 // Set naming convention checkboxes
2812 m_namingConventionStrict->SetValue(docConfig.naming_convention ==
2814 m_namingConventionBalanced->SetValue(docConfig.naming_convention ==
2816 m_namingConventionLoose->SetValue(docConfig.naming_convention == EmberCore::ParserConfig::NamingConvention::LOOSE);
2817
2818 // Tab 2: Tree Elements
2819 const auto &treeConfig = config.GetTreeConfig();
2820 m_behaviorTreeElement->SetValue(treeConfig.behavior_tree_element);
2821 m_treeIdAttr->SetValue(treeConfig.tree_id_attribute);
2822 m_allowMultipleTrees->SetValue(treeConfig.allow_multiple_trees);
2823 m_subtreeElement->SetValue(treeConfig.subtree_element);
2824 m_subtreeRefAttr->SetValue(treeConfig.subtree_reference_attribute);
2825 m_validateStructure->SetValue(treeConfig.validate_tree_structure);
2826 m_requireRootNode->SetValue(treeConfig.require_root_node);
2827
2828 // Tab 3: Node Elements
2829 const auto &nodeConfig = config.GetNodeConfig();
2830 m_controlElement->SetValue(nodeConfig.control_element);
2831 m_actionElement->SetValue(nodeConfig.action_element);
2832 m_conditionElement->SetValue(nodeConfig.condition_element);
2833 m_decoratorElement->SetValue(nodeConfig.decorator_element);
2834 m_genericNodeElement->SetValue(nodeConfig.generic_node_element);
2835 m_nodeIdAttr->SetValue(nodeConfig.node_id_attribute);
2836 m_nodeNameAttr->SetValue(nodeConfig.node_name_attribute);
2837 m_nodeTypeAttr->SetValue(nodeConfig.node_type_attribute);
2838 m_allowCustomAttrs->SetValue(nodeConfig.allow_custom_attributes);
2839
2840 // Tab 4: Node Classification
2841 const auto &classConfig = config.GetClassificationConfig();
2842 m_classificationStrategy->SetSelection(static_cast<int>(classConfig.strategy));
2843
2844 m_controlTypesList->Clear();
2845 for (const auto &type : classConfig.control_types) {
2846 m_controlTypesList->Append(type);
2847 }
2848
2849 m_decoratorTypesList->Clear();
2850 for (const auto &type : classConfig.decorator_types) {
2851 m_decoratorTypesList->Append(type);
2852 }
2853
2854 m_actionTypesList->Clear();
2855 for (const auto &type : classConfig.action_types) {
2856 m_actionTypesList->Append(type);
2857 }
2858
2859 m_conditionTypesList->Clear();
2860 for (const auto &type : classConfig.condition_types) {
2861 m_conditionTypesList->Append(type);
2862 }
2863
2864 m_unknownBehavior->SetSelection(static_cast<int>(classConfig.unknown_behavior));
2865 m_typeAttrName->SetValue(classConfig.type_attribute_name);
2866
2867 // Tab 5: Blackboard
2868 const auto &bbConfig = config.GetBlackboardConfig();
2869 m_blackboardElement->SetValue(bbConfig.blackboard_element);
2870 m_blackboardIdAttr->SetValue(bbConfig.blackboard_id_attribute);
2871 m_blackboardParentAttr->SetValue(bbConfig.parent_attribute);
2872 m_entryElement->SetValue(bbConfig.entry_element);
2873 m_entryKeyAttr->SetValue(bbConfig.entry_key_attribute);
2874 m_entryTypeAttr->SetValue(bbConfig.entry_type_attribute);
2875 m_entryValueAttr->SetValue(bbConfig.entry_value_attribute);
2876 m_allowUndefinedKeys->SetValue(bbConfig.allow_undefined_keys);
2877
2878 // Type mappings
2879 if (m_typeMappingsGrid->GetNumberRows() > 0) {
2880 m_typeMappingsGrid->DeleteRows(0, m_typeMappingsGrid->GetNumberRows());
2881 }
2882 for (const auto &mapping : bbConfig.type_mappings) {
2883 int row = m_typeMappingsGrid->GetNumberRows();
2884 m_typeMappingsGrid->AppendRows(1);
2885 m_typeMappingsGrid->SetCellValue(row, 0, mapping.first);
2886 m_typeMappingsGrid->SetCellValue(row, 1, mapping.second);
2887 }
2888
2889 // Tab 6: Advanced
2890 const auto &advConfig = config.GetAdvancedConfig();
2891 m_enableProgressReporting->SetValue(advConfig.enable_progress_reporting);
2892 m_detailedErrors->SetValue(advConfig.detailed_error_messages);
2893 m_trackLineColumn->SetValue(advConfig.track_line_column);
2894 m_subtreeExpansion->SetSelection(static_cast<int>(advConfig.subtree_expansion));
2895 m_maxRecursionDepth->SetValue(advConfig.max_recursion_depth);
2896 m_xmlNamespace->SetValue(advConfig.xml_namespace_uri);
2897 m_preserveComments->SetValue(advConfig.preserve_comments);
2898 m_preserveAttrOrder->SetValue(advConfig.preserve_attribute_order);
2899 m_customValidation->SetValue(advConfig.custom_validation_rules);
2900
2901 // Enable the custom validation toggle if there are rules loaded
2902 bool hasCustomRules = !advConfig.custom_validation_rules.empty();
2903 m_enableCustomValidation->SetValue(hasCustomRules);
2904 m_customValidation->Enable(hasCustomRules);
2905}
2906
2907// Placeholder event handlers - full implementation would go here
2909 long selectedIndex = m_profileList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2910 if (selectedIndex == -1)
2911 return;
2912
2913 wxString profileName = m_profileList->GetItemText(selectedIndex);
2914
2915 // Remove the "* " prefix if present (active profile marker)
2916 if (profileName.StartsWith(wxString::FromUTF8("* "))) {
2917 profileName = profileName.Mid(2); // Skip first 2 characters
2918 }
2919
2920 // Save the current profile before switching if there are changes
2922
2923 // Now load the new profile
2924 auto &configMgr = EmberCore::ConfigManager::GetInstance();
2925 auto profile = configMgr.GetProfile(profileName.ToStdString());
2926 LoadProfileData(profile);
2927}
2928
2929void ParserConfigDialog::OnNewProfile(wxCommandEvent &event) {
2930 // Save current profile before creating a new one
2932
2933 wxTextEntryDialog dialog(this, "Enter new profile name:", "New Profile");
2934 if (dialog.ShowModal() == wxID_OK) {
2935 wxString name = dialog.GetValue();
2936 if (!name.IsEmpty()) {
2937 auto profile = std::make_shared<EmberCore::ParserProfile>(name.ToStdString(), "");
2938 auto &configMgr = EmberCore::ConfigManager::GetInstance();
2939 if (configMgr.AddProfile(profile)) {
2940 LoadProfiles();
2941
2942 // Find and select the new profile
2943 long index = m_profileList->FindItem(-1, name);
2944 if (index != wxNOT_FOUND) {
2945 m_profileList->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
2946 m_profileList->EnsureVisible(index);
2947 LoadProfileData(profile);
2948 }
2949 }
2950 }
2951 }
2952}
2953
2954void ParserConfigDialog::OnCloneProfile(wxCommandEvent &event) {
2955 if (!m_currentProfile)
2956 return;
2957
2958 // First, save any unsaved changes to the current profile
2960
2961 wxTextEntryDialog dialog(this, "Enter name for cloned profile:", "Clone Profile",
2962 m_currentProfile->GetName() + " (Copy)");
2963 if (dialog.ShowModal() == wxID_OK) {
2964 wxString name = dialog.GetValue();
2965 if (!name.IsEmpty()) {
2966 // Clone the profile with its current (saved) state
2967 auto cloned = m_currentProfile->Clone(name.ToStdString());
2968 auto &configMgr = EmberCore::ConfigManager::GetInstance();
2969 if (configMgr.AddProfile(cloned)) {
2970 LoadProfiles();
2971
2972 // Find and select the cloned profile
2973 long index = m_profileList->FindItem(-1, name);
2974 if (index != wxNOT_FOUND) {
2975 m_profileList->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
2976 m_profileList->EnsureVisible(index);
2977 LoadProfileData(cloned);
2978 }
2979 }
2980 }
2981 }
2982}
2983
2984void ParserConfigDialog::OnDeleteProfile(wxCommandEvent &event) {
2985 long selectedIndex = m_profileList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
2986 if (selectedIndex == -1)
2987 return;
2988
2989 wxString profileName = m_profileList->GetItemText(selectedIndex);
2990
2991 // Remove the "* " prefix if present (active profile marker)
2992 if (profileName.StartsWith(wxString::FromUTF8("* "))) {
2993 profileName = profileName.Mid(2);
2994 }
2995
2996 if (wxMessageBox("Are you sure you want to delete this profile?", "Confirm Delete", wxYES_NO | wxICON_QUESTION,
2997 this) == wxYES) {
2998 auto &configMgr = EmberCore::ConfigManager::GetInstance();
2999 if (configMgr.RemoveProfile(profileName.ToStdString())) {
3000 LoadProfiles();
3001 }
3002 }
3003}
3004
3005void ParserConfigDialog::OnImportProfile(wxCommandEvent &event) {
3006 // Save current profile before importing a new one
3008
3009 // Use the profiles directory as default
3010 auto &configMgr = EmberCore::ConfigManager::GetInstance();
3011 wxString defaultDir = wxString(configMgr.GetProfilesDirectory());
3012
3013 wxFileDialog dialog(this, "Import Profile", defaultDir, "", "JSON files (*.json)|*.json",
3014 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
3015 if (dialog.ShowModal() == wxID_OK) {
3016 if (configMgr.ImportProfile(dialog.GetPath().ToStdString(), false)) {
3017 LoadProfiles();
3018 wxMessageBox("Profile imported successfully", "Success", wxOK | wxICON_INFORMATION, this);
3019 } else {
3020 wxMessageBox("Failed to import profile", "Error", wxOK | wxICON_ERROR, this);
3021 }
3022 }
3023}
3024
3025void ParserConfigDialog::OnExportProfile(wxCommandEvent &event) {
3026 if (!m_currentProfile)
3027 return;
3028
3029 // Update profile with current UI state before exporting
3031
3032 // Use the profiles directory as default
3033 auto &configMgr = EmberCore::ConfigManager::GetInstance();
3034 wxString defaultDir = wxString(configMgr.GetProfilesDirectory());
3035 wxString defaultFilename = m_currentProfile->GetName() + ".json";
3036
3037 wxFileDialog dialog(this, "Export Profile", defaultDir, defaultFilename, "JSON files (*.json)|*.json",
3038 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
3039
3040 if (dialog.ShowModal() == wxID_OK) {
3041 wxString selectedPath = dialog.GetPath();
3042 wxFileName selectedFile(selectedPath);
3043 wxFileName defaultDirPath(defaultDir);
3044
3045 // Check if export location is outside the default profiles directory
3046 bool isOutsideDefaultDir = !selectedFile.GetPath().StartsWith(defaultDirPath.GetFullPath());
3047
3048 if (isOutsideDefaultDir) {
3049 int result = wxMessageBox("You are exporting to a location outside the default profiles directory.\n\n"
3050 "Default: " +
3051 defaultDir +
3052 "\n"
3053 "Selected: " +
3054 selectedFile.GetPath() +
3055 "\n\n"
3056 "Are you sure you want to export here?",
3057 "Confirm Export Location", wxYES_NO | wxICON_QUESTION, this);
3058
3059 if (result != wxYES) {
3060 return; // User cancelled
3061 }
3062 }
3063
3064 if (configMgr.ExportProfile(m_currentProfile->GetName(), selectedPath.ToStdString())) {
3065 wxMessageBox("Profile exported successfully to:\n" + selectedPath, "Success", wxOK | wxICON_INFORMATION,
3066 this);
3067 } else {
3068 wxMessageBox("Failed to export profile", "Error", wxOK | wxICON_ERROR, this);
3069 }
3070 }
3071}
3072
3073void ParserConfigDialog::OnSetActiveProfile(wxCommandEvent &event) {
3074 if (!m_currentProfile)
3075 return;
3076
3077 // Save any changes before setting as active
3079
3080 auto &configMgr = EmberCore::ConfigManager::GetInstance();
3081 configMgr.SetActiveProfile(m_currentProfile->GetName());
3082
3083 // Refresh list to show new active marker (*)
3084 LoadProfiles();
3085
3086 wxMessageBox("Active profile set to: " + m_currentProfile->GetName(), "Success", wxOK | wxICON_INFORMATION, this);
3087}
3088
3089void ParserConfigDialog::OnTestValidate(wxCommandEvent &event) {
3090 wxString errorMsg;
3091 if (ValidateCurrentConfig(errorMsg)) {
3092 wxMessageBox("✓ Configuration is valid!\n\nAll required fields are filled and properly configured.",
3093 "Validation Success", wxOK | wxICON_INFORMATION, this);
3094 } else {
3095 // Use a custom dialog for better error display
3096 wxDialog errorDlg(this, wxID_ANY, "Configuration Validation Failed", wxDefaultPosition, wxSize(800, 600));
3097 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
3098
3099 // Title/icon row
3100 wxBoxSizer *titleSizer = new wxBoxSizer(wxHORIZONTAL);
3101 wxStaticText *titleText = new wxStaticText(&errorDlg, wxID_ANY, "âš  Configuration Validation Errors");
3102 wxFont titleFont = titleText->GetFont();
3103 titleFont.SetPointSize(12);
3104 titleFont.SetWeight(wxFONTWEIGHT_BOLD);
3105 titleText->SetFont(titleFont);
3106 titleText->SetForegroundColour(wxColour(255, 100, 100));
3107 titleSizer->Add(titleText, 0, wxALL, 10);
3108 sizer->Add(titleSizer, 0, wxEXPAND);
3109
3110 // Error message text control
3111 wxTextCtrl *textCtrl = new wxTextCtrl(&errorDlg, wxID_ANY, errorMsg, wxDefaultPosition, wxDefaultSize,
3112 wxTE_MULTILINE | wxTE_READONLY | wxTE_WORDWRAP);
3113
3114 // Use monospace font for better readability
3115 wxFont monoFont(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
3116 textCtrl->SetFont(monoFont);
3117
3118 // Dark theme
3119 textCtrl->SetBackgroundColour(wxColour(50, 50, 50));
3120 textCtrl->SetForegroundColour(wxColour(255, 255, 255));
3121
3122 sizer->Add(textCtrl, 1, wxEXPAND | wxALL, 10);
3123
3124 // Close button
3125 wxButton *closeBtn = new wxButton(&errorDlg, wxID_OK, "Close");
3126 sizer->Add(closeBtn, 0, wxALIGN_CENTER | wxALL, 10);
3127
3128 errorDlg.SetSizer(sizer);
3129 errorDlg.ShowModal();
3130 }
3131}
3132
3133void ParserConfigDialog::OnPreviewJSON(wxCommandEvent &event) {
3134 if (!m_currentProfile)
3135 return;
3136
3137 auto config = GetConfigFromUI();
3138 m_currentProfile->SetConfig(*config);
3139
3140 auto json = m_currentProfile->ToJson();
3141 wxString jsonStr(json.dump(4));
3142
3143 wxDialog previewDlg(this, wxID_ANY, "JSON Preview", wxDefaultPosition, wxSize(600, 500));
3144 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
3145 wxTextCtrl *textCtrl = new wxTextCtrl(&previewDlg, wxID_ANY, jsonStr, wxDefaultPosition, wxDefaultSize,
3146 wxTE_MULTILINE | wxTE_READONLY);
3147 sizer->Add(textCtrl, 1, wxEXPAND | wxALL, 10);
3148 wxButton *closeBtn = new wxButton(&previewDlg, wxID_OK, "Close");
3149 sizer->Add(closeBtn, 0, wxALIGN_CENTER | wxALL, 10);
3150 previewDlg.SetSizer(sizer);
3151 previewDlg.ShowModal();
3152}
3153
3154void ParserConfigDialog::OnResetDefaults(wxCommandEvent &event) {
3155 if (wxMessageBox("Reset current profile to default values?", "Confirm Reset", wxYES_NO | wxICON_QUESTION, this) ==
3156 wxYES) {
3157 auto defaultConfig = EmberCore::ParserConfig::CreateDefault();
3158 SetUIFromConfig(defaultConfig);
3159 m_modified = true;
3160 }
3161}
3162
3163void ParserConfigDialog::OnSave(wxCommandEvent &event) {
3165 EndModal(wxID_OK);
3166}
3167
3168void ParserConfigDialog::OnSaveAs(wxCommandEvent &event) {
3169 wxTextEntryDialog dialog(this, "Enter name for new profile:", "Save As");
3170 if (dialog.ShowModal() == wxID_OK) {
3171 wxString name = dialog.GetValue();
3172 if (!name.IsEmpty()) {
3173 auto profile = std::make_shared<EmberCore::ParserProfile>(name.ToStdString(),
3174 m_profileDescription->GetValue().ToStdString());
3175 auto config = GetConfigFromUI();
3176 profile->SetConfig(*config);
3177
3178 auto &configMgr = EmberCore::ConfigManager::GetInstance();
3179 if (configMgr.AddProfile(profile)) {
3180 m_currentProfile = profile;
3181 LoadProfiles();
3182 wxMessageBox("Profile saved as: " + name, "Success", wxOK | wxICON_INFORMATION, this);
3183 }
3184 }
3185 }
3186}
3187
3188void ParserConfigDialog::OnCancel(wxCommandEvent &event) {
3189 if (m_modified) {
3190 if (wxMessageBox("Discard unsaved changes?", "Confirm Cancel", wxYES_NO | wxICON_QUESTION, this) != wxYES) {
3191 return;
3192 }
3193 }
3194 EndModal(wxID_CANCEL);
3195}
3196
3197// Add/Remove button handlers (simplified implementations)
3198void ParserConfigDialog::OnAddControlType(wxCommandEvent &event) {
3199 wxTextEntryDialog dialog(this, "Enter control type name:", "Add Control Type");
3200 if (dialog.ShowModal() == wxID_OK && !dialog.GetValue().IsEmpty()) {
3201 m_controlTypesList->Append(dialog.GetValue());
3202 m_modified = true;
3203 }
3204}
3205
3206void ParserConfigDialog::OnRemoveControlType(wxCommandEvent &event) {
3207 int sel = m_controlTypesList->GetSelection();
3208 if (sel != wxNOT_FOUND) {
3209 m_controlTypesList->Delete(sel);
3210 m_modified = true;
3211 }
3212}
3213
3214void ParserConfigDialog::OnAddDecoratorType(wxCommandEvent &event) {
3215 wxTextEntryDialog dialog(this, "Enter decorator type name:", "Add Decorator Type");
3216 if (dialog.ShowModal() == wxID_OK && !dialog.GetValue().IsEmpty()) {
3217 m_decoratorTypesList->Append(dialog.GetValue());
3218 m_modified = true;
3219 }
3220}
3221
3223 int sel = m_decoratorTypesList->GetSelection();
3224 if (sel != wxNOT_FOUND) {
3225 m_decoratorTypesList->Delete(sel);
3226 m_modified = true;
3227 }
3228}
3229
3230void ParserConfigDialog::OnAddActionType(wxCommandEvent &event) {
3231 wxTextEntryDialog dialog(this, "Enter action type name:", "Add Action Type");
3232 if (dialog.ShowModal() == wxID_OK && !dialog.GetValue().IsEmpty()) {
3233 m_actionTypesList->Append(dialog.GetValue());
3234 m_modified = true;
3235 }
3236}
3237
3238void ParserConfigDialog::OnRemoveActionType(wxCommandEvent &event) {
3239 int sel = m_actionTypesList->GetSelection();
3240 if (sel != wxNOT_FOUND) {
3241 m_actionTypesList->Delete(sel);
3242 m_modified = true;
3243 }
3244}
3245
3246void ParserConfigDialog::OnAddConditionType(wxCommandEvent &event) {
3247 wxTextEntryDialog dialog(this, "Enter condition type name:", "Add Condition Type");
3248 if (dialog.ShowModal() == wxID_OK && !dialog.GetValue().IsEmpty()) {
3249 m_conditionTypesList->Append(dialog.GetValue());
3250 m_modified = true;
3251 }
3252}
3253
3255 int sel = m_conditionTypesList->GetSelection();
3256 if (sel != wxNOT_FOUND) {
3257 m_conditionTypesList->Delete(sel);
3258 m_modified = true;
3259 }
3260}
3261
3262void ParserConfigDialog::OnAddTypeMapping(wxCommandEvent &event) {
3263 m_typeMappingsGrid->AppendRows(1);
3264 m_modified = true;
3265}
3266
3267void ParserConfigDialog::OnRemoveTypeMapping(wxCommandEvent &event) {
3268 wxArrayInt rows = m_typeMappingsGrid->GetSelectedRows();
3269 if (rows.GetCount() > 0) {
3270 m_typeMappingsGrid->DeleteRows(rows[0], 1);
3271 m_modified = true;
3272 }
3273}
3274
3276 bool enabled = m_enableCustomValidation->GetValue();
3277 m_customValidation->Enable(enabled);
3278
3279 // If disabling, optionally clear the content to make it clear no rules are active
3280 // Uncomment the next line if you want to clear rules when disabling
3281 // if (!enabled) m_customValidation->Clear();
3282
3283 m_modified = true;
3284}
3285
3287 if (!m_currentProfile)
3288 return;
3289
3290 m_currentProfile->SetName(m_profileName->GetValue().ToStdString());
3291 m_currentProfile->SetDescription(m_profileDescription->GetValue().ToStdString());
3292
3293 auto config = GetConfigFromUI();
3294 m_currentProfile->SetConfig(*config);
3295
3296 auto &configMgr = EmberCore::ConfigManager::GetInstance();
3297 configMgr.SaveProfile(m_currentProfile->GetName());
3298
3299 m_modified = false;
3300}
3301
3307
3308std::shared_ptr<EmberCore::ParserConfig> ParserConfigDialog::GetConfigFromUI() {
3309 auto config = std::make_shared<EmberCore::ParserConfig>();
3310
3311 // Tab 1: Document Structure
3312 auto &docConfig = config->GetDocumentConfig();
3313 docConfig.root_element = m_rootElement->GetValue().ToStdString();
3314 docConfig.main_tree_attribute = m_mainTreeAttr->GetValue().ToStdString();
3315 docConfig.case_sensitive = m_caseSensitive->GetValue();
3316 docConfig.allow_unknown_elements = m_allowUnknownElements->GetValue();
3317 docConfig.whitespace_handling =
3319
3320 // Get naming convention from checkboxes
3321 if (m_namingConventionStrict->GetValue()) {
3322 docConfig.naming_convention = EmberCore::ParserConfig::NamingConvention::STRICT;
3323 } else if (m_namingConventionBalanced->GetValue()) {
3324 docConfig.naming_convention = EmberCore::ParserConfig::NamingConvention::BALANCED;
3325 } else if (m_namingConventionLoose->GetValue()) {
3326 docConfig.naming_convention = EmberCore::ParserConfig::NamingConvention::LOOSE;
3327 } else {
3328 // Default to BALANCED if none is selected
3329 docConfig.naming_convention = EmberCore::ParserConfig::NamingConvention::BALANCED;
3330 }
3331
3332 // Tab 2: Tree Elements
3333 auto &treeConfig = config->GetTreeConfig();
3334 treeConfig.behavior_tree_element = m_behaviorTreeElement->GetValue().ToStdString();
3335 treeConfig.tree_id_attribute = m_treeIdAttr->GetValue().ToStdString();
3336 treeConfig.allow_multiple_trees = m_allowMultipleTrees->GetValue();
3337 treeConfig.subtree_element = m_subtreeElement->GetValue().ToStdString();
3338 treeConfig.subtree_reference_attribute = m_subtreeRefAttr->GetValue().ToStdString();
3339 treeConfig.validate_tree_structure = m_validateStructure->GetValue();
3340 treeConfig.require_root_node = m_requireRootNode->GetValue();
3341
3342 // Tab 3: Node Elements
3343 auto &nodeConfig = config->GetNodeConfig();
3344 nodeConfig.control_element = m_controlElement->GetValue().ToStdString();
3345 nodeConfig.action_element = m_actionElement->GetValue().ToStdString();
3346 nodeConfig.condition_element = m_conditionElement->GetValue().ToStdString();
3347 nodeConfig.decorator_element = m_decoratorElement->GetValue().ToStdString();
3348 nodeConfig.generic_node_element = m_genericNodeElement->GetValue().ToStdString();
3349 nodeConfig.node_id_attribute = m_nodeIdAttr->GetValue().ToStdString();
3350 nodeConfig.node_name_attribute = m_nodeNameAttr->GetValue().ToStdString();
3351 nodeConfig.node_type_attribute = m_nodeTypeAttr->GetValue().ToStdString();
3352 nodeConfig.allow_custom_attributes = m_allowCustomAttrs->GetValue();
3353
3354 // Tab 4: Node Classification
3355 auto &classConfig = config->GetClassificationConfig();
3356 classConfig.strategy =
3358
3359 classConfig.control_types.clear();
3360 for (unsigned int i = 0; i < m_controlTypesList->GetCount(); i++) {
3361 classConfig.control_types.push_back(m_controlTypesList->GetString(i).ToStdString());
3362 }
3363
3364 classConfig.decorator_types.clear();
3365 for (unsigned int i = 0; i < m_decoratorTypesList->GetCount(); i++) {
3366 classConfig.decorator_types.push_back(m_decoratorTypesList->GetString(i).ToStdString());
3367 }
3368
3369 classConfig.action_types.clear();
3370 for (unsigned int i = 0; i < m_actionTypesList->GetCount(); i++) {
3371 classConfig.action_types.push_back(m_actionTypesList->GetString(i).ToStdString());
3372 }
3373
3374 classConfig.condition_types.clear();
3375 for (unsigned int i = 0; i < m_conditionTypesList->GetCount(); i++) {
3376 classConfig.condition_types.push_back(m_conditionTypesList->GetString(i).ToStdString());
3377 }
3378
3379 classConfig.unknown_behavior =
3381 classConfig.type_attribute_name = m_typeAttrName->GetValue().ToStdString();
3382
3383 // Tab 5: Blackboard
3384 auto &bbConfig = config->GetBlackboardConfig();
3385 bbConfig.blackboard_element = m_blackboardElement->GetValue().ToStdString();
3386 bbConfig.blackboard_id_attribute = m_blackboardIdAttr->GetValue().ToStdString();
3387 bbConfig.parent_attribute = m_blackboardParentAttr->GetValue().ToStdString();
3388 bbConfig.entry_element = m_entryElement->GetValue().ToStdString();
3389 bbConfig.entry_key_attribute = m_entryKeyAttr->GetValue().ToStdString();
3390 bbConfig.entry_type_attribute = m_entryTypeAttr->GetValue().ToStdString();
3391 bbConfig.entry_value_attribute = m_entryValueAttr->GetValue().ToStdString();
3392 bbConfig.allow_undefined_keys = m_allowUndefinedKeys->GetValue();
3393
3394 bbConfig.type_mappings.clear();
3395 for (int i = 0; i < m_typeMappingsGrid->GetNumberRows(); i++) {
3396 EmberCore::String xmlType = m_typeMappingsGrid->GetCellValue(i, 0).ToStdString();
3397 EmberCore::String internalType = m_typeMappingsGrid->GetCellValue(i, 1).ToStdString();
3398 if (!xmlType.empty() && !internalType.empty()) {
3399 bbConfig.type_mappings[xmlType] = internalType;
3400 }
3401 }
3402
3403 // Tab 6: Advanced
3404 auto &advConfig = config->GetAdvancedConfig();
3405 advConfig.enable_progress_reporting = m_enableProgressReporting->GetValue();
3406 advConfig.detailed_error_messages = m_detailedErrors->GetValue();
3407 advConfig.track_line_column = m_trackLineColumn->GetValue();
3408 advConfig.subtree_expansion =
3410 advConfig.max_recursion_depth = m_maxRecursionDepth->GetValue();
3411 advConfig.xml_namespace_uri = m_xmlNamespace->GetValue().ToStdString();
3412 advConfig.preserve_comments = m_preserveComments->GetValue();
3413 advConfig.preserve_attribute_order = m_preserveAttrOrder->GetValue();
3414
3415 // Only save custom validation rules if the toggle is enabled
3416 if (m_enableCustomValidation->GetValue()) {
3417 advConfig.custom_validation_rules = m_customValidation->GetValue().ToStdString();
3418 } else {
3419 advConfig.custom_validation_rules = "";
3420 }
3421
3422 return config;
3423}
3424
3426 bool hasErrors = false;
3427 int errorCount = 0;
3428 wxString detailedErrors;
3429
3430 // Helper lambda to add formatted error
3431 auto addError = [&](const wxString &page, const wxString &field, const wxString &issue) {
3432 errorCount++;
3433 detailedErrors += wxString::Format("%d. [Page: %s]\n"
3434 " Field: %s\n"
3435 " Issue: %s\n\n",
3436 errorCount, page, field, issue);
3437 hasErrors = true;
3438 };
3439
3440 // ========================================================================
3441 // PAGE 1: DOCUMENT STRUCTURE
3442 // ========================================================================
3443
3445 addError("Document Structure", "Root Element",
3446 "Required field is empty. Specify the XML root element name (e.g., 'root')");
3447 } else if (HasInvalidCharacters(m_rootElement)) {
3448 addError("Document Structure", "Root Element",
3449 wxString::Format("Invalid characters detected. XML names must start with a letter or underscore, "
3450 "and can ONLY contain letters (a-z, A-Z) and underscores (_). "
3451 "NO digits, hyphens, periods, or special characters allowed. Current value: '%s'",
3452 m_rootElement->GetValue()));
3453 }
3454
3456 addError("Document Structure", "Main Tree Attribute",
3457 "Required field is empty. Specify the attribute that identifies the main tree");
3459 addError(
3460 "Document Structure", "Main Tree Attribute",
3461 wxString::Format("Invalid characters detected. Only letters and underscores allowed. Current value: '%s'",
3462 m_mainTreeAttr->GetValue()));
3463 }
3464
3465 // ========================================================================
3466 // PAGE 2: TREE ELEMENTS
3467 // ========================================================================
3468
3470 addError("Tree Elements", "Behavior Tree Element",
3471 "Required field is empty. Specify the element name for behavior trees (e.g., 'BehaviorTree')");
3473 addError("Tree Elements", "Behavior Tree Element",
3474 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3475 m_behaviorTreeElement->GetValue()));
3476 }
3477
3479 addError("Tree Elements", "Tree ID Attribute",
3480 "Required field is empty. Specify the attribute that holds tree IDs (e.g., 'ID' or 'name')");
3481 } else if (HasInvalidCharacters(m_treeIdAttr)) {
3482 addError("Tree Elements", "Tree ID Attribute",
3483 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3484 m_treeIdAttr->GetValue()));
3485 }
3486
3488 addError("Tree Elements", "SubTree Element",
3489 "Required field is empty. Specify the element name for subtree references (e.g., 'SubTree')");
3491 addError("Tree Elements", "SubTree Element",
3492 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3493 m_subtreeElement->GetValue()));
3494 }
3495
3497 addError("Tree Elements", "SubTree Reference Attribute",
3498 "Required field is empty. Specify the attribute that references other trees (e.g., 'ID')");
3500 addError("Tree Elements", "SubTree Reference Attribute",
3501 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3502 m_subtreeRefAttr->GetValue()));
3503 }
3504
3505 // ========================================================================
3506 // PAGE 3: NODE ELEMENTS
3507 // ========================================================================
3508
3510 addError("Node Elements", "Control Node Element",
3511 "Required field is empty. Specify element name for control nodes (e.g., 'Control' or leave generic)");
3513 addError("Node Elements", "Control Node Element",
3514 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3515 m_controlElement->GetValue()));
3516 }
3517
3519 addError("Node Elements", "Action Node Element",
3520 "Required field is empty. Specify element name for action nodes (e.g., 'Action')");
3522 addError("Node Elements", "Action Node Element",
3523 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3524 m_actionElement->GetValue()));
3525 }
3526
3528 addError("Node Elements", "Condition Node Element",
3529 "Required field is empty. Specify element name for condition nodes (e.g., 'Condition')");
3531 addError("Node Elements", "Condition Node Element",
3532 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3533 m_conditionElement->GetValue()));
3534 }
3535
3537 addError("Node Elements", "Decorator Node Element",
3538 "Required field is empty. Specify element name for decorator nodes (e.g., 'Decorator')");
3540 addError("Node Elements", "Decorator Node Element",
3541 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3542 m_decoratorElement->GetValue()));
3543 }
3544
3546 addError("Node Elements", "Node Name Attribute",
3547 "Required field is empty. Specify the attribute for node names (e.g., 'name')");
3549 addError("Node Elements", "Node Name Attribute",
3550 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3551 m_nodeNameAttr->GetValue()));
3552 }
3553
3554 // ========================================================================
3555 // PAGE 4: BLACKBOARD
3556 // ========================================================================
3557
3559 addError("Blackboard", "Blackboard Element",
3560 "Required field is empty. Specify element name for blackboard (e.g., 'Blackboard')");
3562 addError("Blackboard", "Blackboard Element",
3563 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3564 m_blackboardElement->GetValue()));
3565 }
3566
3568 addError("Blackboard", "Blackboard ID Attribute",
3569 "Required field is empty. Specify the attribute for blackboard IDs (e.g., 'ID')");
3571 addError("Blackboard", "Blackboard ID Attribute",
3572 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3573 m_blackboardIdAttr->GetValue()));
3574 }
3575
3577 addError("Blackboard", "Entry Element",
3578 "Required field is empty. Specify element name for blackboard entries (e.g., 'Entry')");
3580 addError("Blackboard", "Entry Element",
3581 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3582 m_entryElement->GetValue()));
3583 }
3584
3586 addError("Blackboard", "Entry Key Attribute",
3587 "Required field is empty. Specify the attribute for entry keys (e.g., 'key')");
3589 addError("Blackboard", "Entry Key Attribute",
3590 wxString::Format("Invalid characters. Only letters and underscores allowed. Current value: '%s'",
3591 m_entryKeyAttr->GetValue()));
3592 }
3593
3594 // ========================================================================
3595 // PAGE 5: NODE CLASSIFICATION
3596 // ========================================================================
3597
3598 // Strategy must be selected
3599 if (m_classificationStrategy && m_classificationStrategy->GetSelection() == wxNOT_FOUND) {
3600 addError("Node Classification", "Classification Strategy",
3601 "No strategy selected. Choose: Element Name, Attribute-based, or Hybrid");
3602 }
3603
3604 // Check if type lists are needed but empty
3605 if (m_classificationStrategy && (m_classificationStrategy->GetSelection() == 0 ||
3606 m_classificationStrategy->GetSelection() == 2)) { // Element Name or Hybrid
3607
3608 if (m_controlTypesList && m_controlTypesList->GetCount() == 0) {
3609 addError("Node Classification", "Control Node Types List",
3610 "Using Element Name classification but control types list is empty. Add control node types (e.g., "
3611 "Sequence, Selector)");
3612 }
3613
3614 if (m_actionTypesList && m_actionTypesList->GetCount() == 0) {
3615 addError("Node Classification", "Action Node Types List",
3616 "Using Element Name classification but action types list is empty. Add action node types");
3617 }
3618
3619 if (m_conditionTypesList && m_conditionTypesList->GetCount() == 0) {
3620 addError("Node Classification", "Condition Node Types List",
3621 "Using Element Name classification but condition types list is empty. Add condition node types");
3622 }
3623
3624 if (m_decoratorTypesList && m_decoratorTypesList->GetCount() == 0) {
3625 addError("Node Classification", "Decorator Node Types List",
3626 "Using Element Name classification but decorator types list is empty. Add decorator node types");
3627 }
3628 }
3629
3630 // Check type attribute if using attribute-based classification
3631 if (m_classificationStrategy && (m_classificationStrategy->GetSelection() == 1 ||
3632 m_classificationStrategy->GetSelection() == 2)) { // Attribute or Hybrid
3633
3635 addError("Node Classification", "Type Attribute Name",
3636 "Using Attribute-based classification but type attribute is empty. Specify attribute name (e.g., "
3637 "'type')");
3638 }
3639 }
3640
3641 // ========================================================================
3642 // CROSS-PAGE VALIDATION
3643 // ========================================================================
3644
3645 // Check for duplicate element names across pages
3646 wxString btElement = m_behaviorTreeElement ? m_behaviorTreeElement->GetValue().Trim() : "";
3647 wxString stElement = m_subtreeElement ? m_subtreeElement->GetValue().Trim() : "";
3648 wxString bbElement = m_blackboardElement ? m_blackboardElement->GetValue().Trim() : "";
3649
3650 if (!btElement.IsEmpty() && btElement == stElement && btElement != "SubTree") {
3651 addError("Tree Elements", "Element Name Conflict",
3652 wxString::Format("BehaviorTree and SubTree use same element name '%s'. They should be different",
3653 btElement));
3654 }
3655
3656 if (!btElement.IsEmpty() && btElement == bbElement) {
3657 addError("Configuration Conflict", "Element Name Conflict",
3658 wxString::Format("BehaviorTree and Blackboard use same element name '%s'. They should be different",
3659 btElement));
3660 }
3661
3662 // ========================================================================
3663 // BUILD FINAL ERROR MESSAGE
3664 // ========================================================================
3665
3666 if (hasErrors) {
3667 errorMsg = wxString::Format("VALIDATION FAILED - Found %d error(s)\n"
3668 "========================================\n\n"
3669 "%s"
3670 "Please fix these issues before saving the configuration.\n"
3671 "Tip: Empty required fields are highlighted with a pulsing yellow background.",
3672 errorCount, detailedErrors);
3673 return false;
3674 }
3675
3676 // If UI validation passes, also run the config-level validation
3677 auto config = GetConfigFromUI();
3678 std::vector<EmberCore::String> configErrors;
3679
3680 if (!config->Validate(configErrors)) {
3681 errorMsg = "ADDITIONAL VALIDATION ERRORS\n"
3682 "========================================\n\n";
3683 for (const auto &err : configErrors) {
3684 errorMsg += wxString::Format("- %s\n", wxString(err));
3685 }
3686 return false;
3687 }
3688
3689 return true;
3690}
3691
3693
3695 m_notebook->Enable(enable);
3696 m_profileName->Enable(enable);
3697 m_profileDescription->Enable(enable);
3698 m_saveBtn->Enable(enable);
3699 m_saveAsBtn->Enable(enable);
3700}
3701
3703 long selectedIndex = m_profileList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
3704 if (selectedIndex == -1)
3705 return wxEmptyString;
3706 return m_profileList->GetItemText(selectedIndex);
3707}
3708
3709// ============================================================================
3710// PULSE ANIMATION IMPLEMENTATION
3711// ============================================================================
3712
3714 if (m_pulseTimer) {
3715 m_pulseTimer->Stop();
3716 delete m_pulseTimer;
3717 }
3718}
3719
3720void ParserConfigDialog::OnPulseTimer(wxTimerEvent &event) {
3721 // Increment phase (0.0 to 1.0, then wrap)
3722 m_pulsePhase += 0.02f; // Smooth increment
3723 if (m_pulsePhase > 1.0f) {
3724 m_pulsePhase = 0.0f;
3725 }
3726
3728}
3729
3731 // Use sinusoidal easing for smooth, natural pulse
3732 // sin gives values from 0 to 1 to 0 smoothly
3733 float t = std::sin(m_pulsePhase * 3.14159f * 2.0f) * 0.5f + 0.5f;
3734
3735 // List of all required text fields that should pulse when empty OR invalid
3736 wxTextCtrl *textFields[] = {m_rootElement, m_mainTreeAttr, m_behaviorTreeElement, m_treeIdAttr,
3742
3743 // Update each text field
3744 for (wxTextCtrl *field : textFields) {
3745 if (field && (IsFieldEmpty(field) || HasInvalidCharacters(field))) {
3746 // Field is empty OR has invalid characters - pulse yellow
3747 wxColour pulseColor = InterpolateColor(m_normalBg, m_highlightBg, t);
3748 field->SetBackgroundColour(pulseColor);
3749 field->Refresh();
3750 } else if (field) {
3751 // Field has valid content, use normal background
3752 field->SetBackgroundColour(m_normalBg);
3753 field->Refresh();
3754 }
3755 }
3756}
3757
3758wxColour ParserConfigDialog::InterpolateColor(const wxColour &color1, const wxColour &color2, float t) {
3759 // Clamp t to [0, 1]
3760 if (t < 0.0f)
3761 t = 0.0f;
3762 if (t > 1.0f)
3763 t = 1.0f;
3764
3765 // Linear interpolation of RGB components
3766 int r = static_cast<int>(color1.Red() + (color2.Red() - color1.Red()) * t);
3767 int g = static_cast<int>(color1.Green() + (color2.Green() - color1.Green()) * t);
3768 int b = static_cast<int>(color1.Blue() + (color2.Blue() - color1.Blue()) * t);
3769
3770 return wxColour(r, g, b);
3771}
3772
3773bool ParserConfigDialog::IsFieldEmpty(wxTextCtrl *field) {
3774 if (!field)
3775 return false;
3776 return field->GetValue().Trim().IsEmpty();
3777}
3778
3780 if (!field)
3781 return false;
3782 return field->GetSelection() == wxNOT_FOUND;
3783}
3784
3786 if (!field)
3787 return false;
3788
3789 wxString value = field->GetValue().Trim();
3790 if (value.IsEmpty())
3791 return false; // Empty is handled separately
3792
3793 // Use current naming convention for validation
3795}
3796
3800 } else if (m_namingConventionLoose && m_namingConventionLoose->GetValue()) {
3802 } else {
3803 // Default to BALANCED (including when m_namingConventionBalanced is checked)
3805 }
3806}
3807
3808bool ParserConfigDialog::IsValidXMLName(const wxString &name) {
3809 // Use current naming convention
3811}
3812
3814 if (name.IsEmpty())
3815 return true; // Empty is valid here (checked separately)
3816
3817 switch (convention) {
3819 // STRICT: Only letters (a-z, A-Z) and underscores (_)
3820 // - Must start with a letter or underscore
3821 // - Can ONLY contain letters and underscores
3822 // - NO digits, hyphens, or any other characters
3823 {
3824 wxChar firstChar = name[0];
3825 if (!wxIsalpha(firstChar) && firstChar != '_') {
3826 return false;
3827 }
3828
3829 for (size_t i = 0; i < name.Length(); i++) {
3830 wxChar c = name[i];
3831 if (!wxIsalpha(c) && c != '_') {
3832 return false;
3833 }
3834 }
3835 return true;
3836 }
3837
3839 // BALANCED: Letters, underscores, digits, and hyphens
3840 // - Must start with a letter or underscore
3841 // - Can contain letters, digits, underscores, and hyphens
3842 {
3843 wxChar firstChar = name[0];
3844 if (!wxIsalpha(firstChar) && firstChar != '_') {
3845 return false;
3846 }
3847
3848 for (size_t i = 0; i < name.Length(); i++) {
3849 wxChar c = name[i];
3850 if (!wxIsalpha(c) && !wxIsdigit(c) && c != '_' && c != '-') {
3851 return false;
3852 }
3853 }
3854 return true;
3855 }
3856
3858 // LOOSE: Follow XML 1.0 name rules (most permissive)
3859 // - Must start with letter, underscore, or colon
3860 // - Can contain letters, digits, underscores, hyphens, periods, colons
3861 // - Avoid only problematic characters like <, >, &, quotes, spaces
3862 {
3863 wxChar firstChar = name[0];
3864 // XML names can start with: letter, underscore, or colon
3865 if (!wxIsalpha(firstChar) && firstChar != '_' && firstChar != ':') {
3866 return false;
3867 }
3868
3869 for (size_t i = 0; i < name.Length(); i++) {
3870 wxChar c = name[i];
3871
3872 // Reject XML-breaking characters
3873 if (c == '<' || c == '>' || c == '&' || c == '"' || c == '\'' || wxIsspace(c) || c == '/' ||
3874 c == '\\' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '=' ||
3875 c == '!' || c == '?' || c == '*' || c == '+' || c == '#' || c == '@' || c == '$' || c == '%' ||
3876 c == '^' || c == '~' || c == '`' || c == '|' || c == ';' || c == ',' ||
3877 c < 32) { // Also reject control characters
3878 return false;
3879 }
3880 }
3881 return true;
3882 }
3883
3884 default:
3885 return false;
3886 }
3887}
3888
3889void ParserConfigDialog::OnFieldChanged(wxCommandEvent &event) {
3890 // When a field changes, immediately update its color
3891 // (This ensures instant feedback when user starts typing)
3893 m_modified = true; // Mark profile as modified
3894 event.Skip();
3895}
3896
3897void ParserConfigDialog::OnAnyControlChanged(wxCommandEvent &event) {
3898 // Generic handler for checkboxes, choice controls, etc.
3899 m_modified = true;
3900 event.Skip();
3901}
3902
3904 // When naming convention changes, re-validate all fields with new rules
3905 // This will highlight fields that are now invalid under the new convention
3907 m_modified = true;
3908 event.Skip();
3909}
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
MainFrame::OnExit MainFrame::OnNewProject MainFrame::OnCloseProject MainFrame::OnToggleMaximize MainFrame::OnPreviousScene MainFrame::OnPreferences MainFrame::OnEditorTool EVT_BUTTON(ID_MonitorTool, MainFrame::OnMonitorTool) EVT_PAINT(MainFrame
Definition MainFrame.cpp:63
static wxString GetResourcePath(const wxString &relativePath)
ParserConfigDialog::OnProfileSelected EVT_BUTTON(ID_NEW_PROFILE, ParserConfigDialog::OnNewProfile) EVT_BUTTON(ID_CLONE_PROFILE
wxBEGIN_EVENT_TABLE(ParserConfigDialog, EmberUI::ScalableDialog) EVT_LIST_ITEM_SELECTED(ID_PROFILE_LIST
ParserConfigDialog::OnProfileSelected ParserConfigDialog::OnCloneProfile ParserConfigDialog::OnImportProfile ParserConfigDialog::OnSetActiveProfile ParserConfigDialog::OnRemoveControlType ParserConfigDialog::OnRemoveDecoratorType ParserConfigDialog::OnRemoveActionType EVT_CHECKBOX(ID_TOGGLE_CUSTOM_VALIDATION, ParserConfigDialog::OnToggleCustomValidation) EVT_TIMER(ID_PULSE_TIMER
Centralized resource path management for EmberForge.
static ConfigManager & GetInstance()
Configuration for XML parser behavior and element/attribute mappings.
SubTreeExpansion
SubTree expansion strategy.
NamingConvention
Naming convention strictness.
DocumentConfig & GetDocumentConfig()
ClassificationStrategy
Strategy for classifying node types.
ClassificationConfig & GetClassificationConfig()
static ParserConfig CreateDefault()
AdvancedConfig & GetAdvancedConfig()
UnknownTypeBehavior
How to handle unknown/unmapped types.
WhitespaceHandling
Whitespace handling strategy.
TreeConfig & GetTreeConfig()
BlackboardConfig & GetBlackboardConfig()
NodeConfig & GetNodeConfig()
static wxString Get(const wxString &relativePath)
Get the full path to a resource file.
DPI-aware dialog base class for scalable layouts.
Simple scrollable dialog for displaying help text.
Comprehensive dialog for managing parser configuration profiles.
void OnSaveAs(wxCommandEvent &event)
wxCheckBox * m_allowUndefinedKeys
wxCheckBox * m_allowCustomAttrs
void OnAddControlType(wxCommandEvent &event)
wxTextCtrl * m_genericNodeElement
wxTextCtrl * m_blackboardParentAttr
wxPanel * CreateNodeClassificationTab(wxNotebook *notebook)
void OnRemoveConditionType(wxCommandEvent &event)
wxTextCtrl * m_entryValueAttr
void SetUIFromConfig(const EmberCore::ParserConfig &config)
wxTextCtrl * m_subtreeRefAttr
wxChoice * m_classificationStrategy
wxCheckBox * m_enableProgressReporting
wxTextCtrl * m_conditionElement
wxPanel * CreateNodeElementsTab(wxNotebook *notebook)
void OnRemoveActionType(wxCommandEvent &event)
wxTextCtrl * m_decoratorElement
void OnNewProfile(wxCommandEvent &event)
wxTextCtrl * m_blackboardElement
wxListBox * m_decoratorTypesList
void OnFieldChanged(wxCommandEvent &event)
void OnSetActiveProfile(wxCommandEvent &event)
wxTextCtrl * m_profileDescription
void OnAddActionType(wxCommandEvent &event)
wxCheckBox * m_preserveComments
wxPanel * CreateDocumentStructureTab(wxNotebook *notebook)
wxCheckBox * m_trackLineColumn
void OnExportProfile(wxCommandEvent &event)
wxString GetSelectedProfileName() const
wxCheckBox * m_namingConventionLoose
wxCheckBox * m_requireRootNode
wxListBox * m_conditionTypesList
wxTextCtrl * m_controlElement
void OnCloneProfile(wxCommandEvent &event)
wxButton * m_removeControlTypeBtn
wxButton * m_removeConditionTypeBtn
wxCheckBox * m_namingConventionStrict
void OnAnyControlChanged(wxCommandEvent &event)
void OnDeleteProfile(wxCommandEvent &event)
wxCheckBox * m_validateStructure
wxCheckBox * m_allowMultipleTrees
wxTextCtrl * m_blackboardIdAttr
wxCheckBox * m_allowUnknownElements
void OnToggleCustomValidation(wxCommandEvent &event)
bool HasInvalidCharacters(wxTextCtrl *field)
void OnPreviewJSON(wxCommandEvent &event)
void OnPulseTimer(wxTimerEvent &event)
wxTextCtrl * m_behaviorTreeElement
void OnCancel(wxCommandEvent &event)
void EnableProfileControls(bool enable)
bool IsValidXMLName(const wxString &name)
void OnRemoveTypeMapping(wxCommandEvent &event)
wxButton * m_removeDecoratorTypeBtn
wxPanel * CreateBlackboardTab(wxNotebook *notebook)
void OnAddTypeMapping(wxCommandEvent &event)
std::shared_ptr< EmberCore::ParserProfile > m_currentProfile
wxCheckBox * m_enableCustomValidation
wxCheckBox * m_preserveAttrOrder
void OnAddConditionType(wxCommandEvent &event)
bool IsFieldEmpty(wxTextCtrl *field)
void OnRemoveControlType(wxCommandEvent &event)
void OnTestValidate(wxCommandEvent &event)
EmberCore::ParserConfig::NamingConvention GetCurrentNamingConvention() const
void OnAddDecoratorType(wxCommandEvent &event)
wxPanel * CreateLeftSidebar(wxWindow *parent)
wxTextCtrl * m_subtreeElement
bool ValidateCurrentConfig(wxString &errorMsg)
wxTextCtrl * m_customValidation
wxCheckBox * m_namingConventionBalanced
void OnImportProfile(wxCommandEvent &event)
wxPanel * CreateTreeElementsTab(wxNotebook *notebook)
wxPanel * CreateBottomPanel(wxWindow *parent)
void OnNamingConventionChanged(wxCommandEvent &event)
void OnResetDefaults(wxCommandEvent &event)
wxSpinCtrl * m_maxRecursionDepth
wxColour InterpolateColor(const wxColour &color1, const wxColour &color2, float t)
void OnRemoveDecoratorType(wxCommandEvent &event)
void LoadProfileData(std::shared_ptr< EmberCore::ParserProfile > profile)
void OnSave(wxCommandEvent &event)
wxPanel * CreateAdvancedTab(wxNotebook *notebook)
wxCheckBox * m_detailedErrors
void OnProfileSelected(wxListEvent &event)
std::shared_ptr< EmberCore::ParserConfig > GetConfigFromUI()
wxPanel * CreateRightPanel(wxWindow *parent)
std::string String
Framework-agnostic string type.
Definition String.h:14
Definition Panel.h:8