579 dc.SetBackground(wxBrush(wxColour(40, 40, 40)));
583 dc.SetTextForeground(wxColour(150, 150, 150));
584 dc.SetFont(wxFont(11, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_NORMAL));
585 dc.DrawText(
"Run validation to see blackboard data", 20, 20);
594 if (panelWidth < 300)
596 int contentWidth = panelWidth - 30;
600 int summaryHeight = 80;
601 dc.SetBrush(wxBrush(wxColour(50, 50, 60)));
602 dc.SetPen(wxPen(wxColour(100, 130, 200), 1));
603 dc.DrawRoundedRectangle(x, y, contentWidth, summaryHeight, 5);
605 dc.SetTextForeground(wxColour(130, 180, 255));
606 dc.SetFont(wxFont(11, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
607 dc.DrawText(
"Blackboard Summary", x + 10, y + 8);
610 int totalBBs = 0, totalFiles = 0;
611 for (
const auto &rs : report.resource_statuses) {
612 if (rs.has_blackboards) {
614 totalBBs += rs.blackboard_count;
618 dc.SetFont(wxFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
619 dc.SetTextForeground(wxColour(200, 200, 200));
620 dc.DrawText(wxString::Format(
"Total Blackboards: %d across %d file(s)", totalBBs, totalFiles), x + 15, y + 30);
622 if (!report.unresolved_blackboard_includes.empty()) {
623 dc.SetTextForeground(wxColour(255, 180, 50));
624 wxString unresolvedStr =
"Unresolved includes: ";
625 for (
size_t i = 0; i < report.unresolved_blackboard_includes.size(); ++i) {
627 unresolvedStr +=
", ";
628 unresolvedStr += report.unresolved_blackboard_includes[i];
630 dc.DrawText(unresolvedStr, x + 15, y + 50);
632 dc.SetTextForeground(wxColour(100, 255, 100));
633 dc.DrawText(
"All blackboard includes resolved", x + 15, y + 50);
636 y += summaryHeight + 15;
641 for (
const auto &rs : report.resource_statuses) {
642 if (!rs.has_blackboards)
646 wxFileName fn(wxString::FromUTF8(rs.filepath));
647 wxString filename = fn.GetFullName();
649 dc.SetBrush(wxBrush(wxColour(60, 50, 70)));
650 dc.SetPen(wxPen(wxColour(150, 100, 200), 1));
651 dc.DrawRoundedRectangle(x, y, contentWidth, 28, 4);
653 dc.SetTextForeground(wxColour(200, 160, 255));
654 dc.SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
655 dc.DrawText(filename, x + 10, y + 5);
657 dc.SetTextForeground(wxColour(150, 150, 150));
658 dc.SetFont(wxFont(8, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
659 wxString countStr = wxString::Format(
"(%d blackboard(s))", rs.blackboard_count);
660 dc.DrawText(countStr, x + contentWidth - 130, y + 7);
665 std::string resolvedPath =
m_project->ResolveResourcePath(rs.filepath);
666 xmlDocPtr doc = xmlReadFile(resolvedPath.c_str(),
nullptr, XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
668 dc.SetTextForeground(wxColour(255, 100, 100));
669 dc.DrawText(
"Could not parse file for blackboard details", x + 20, y);
674 xmlNodePtr root = xmlDocGetRootElement(doc);
680 for (xmlNodePtr child = root->children; child; child = child->next) {
681 if (child->type != XML_ELEMENT_NODE)
683 const char *nodeName =
reinterpret_cast<const char *
>(child->name);
684 if (!nodeName || strcmp(nodeName,
"Blackboard") != 0)
689 xmlChar *idAttr = xmlGetProp(child,
reinterpret_cast<const xmlChar *
>(
"ID"));
691 bbId =
reinterpret_cast<const char *
>(idAttr);
696 std::string includesStr;
697 xmlChar *incAttr = xmlGetProp(child,
reinterpret_cast<const xmlChar *
>(
"includes"));
699 includesStr =
reinterpret_cast<const char *
>(incAttr);
705 std::string key, type, value;
707 std::vector<BBEntry> entries;
708 for (xmlNodePtr entryNode = child->children; entryNode; entryNode = entryNode->next) {
709 if (entryNode->type != XML_ELEMENT_NODE)
711 const char *eName =
reinterpret_cast<const char *
>(entryNode->name);
712 if (!eName || strcmp(eName,
"Entry") != 0)
716 xmlChar *keyA = xmlGetProp(entryNode,
reinterpret_cast<const xmlChar *
>(
"key"));
718 e.key =
reinterpret_cast<const char *
>(keyA);
721 xmlChar *typeA = xmlGetProp(entryNode,
reinterpret_cast<const xmlChar *
>(
"type"));
723 e.type =
reinterpret_cast<const char *
>(typeA);
726 xmlChar *valA = xmlGetProp(entryNode,
reinterpret_cast<const xmlChar *
>(
"value"));
728 e.value =
reinterpret_cast<const char *
>(valA);
731 entries.push_back(e);
736 int cardHeaderH = 26;
737 dc.SetBrush(wxBrush(wxColour(70, 50, 90)));
738 dc.SetPen(wxPen(wxColour(150, 100, 200), 1));
739 dc.DrawRoundedRectangle(x + 10, y, contentWidth - 20, cardHeaderH, 3);
741 dc.SetTextForeground(wxColour(220, 180, 255));
742 dc.SetFont(wxFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
743 wxString headerText = wxString::Format(
"[%s] (%zu entries)", bbId, entries.size());
744 dc.DrawText(headerText, x + 20, y + 4);
746 if (!includesStr.empty()) {
747 dc.SetTextForeground(wxColour(180, 150, 200));
748 dc.SetFont(wxFont(8, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
749 wxString incText =
"includes: " + wxString::FromUTF8(includesStr);
751 int maxW = contentWidth - 200;
752 wxSize ext = dc.GetTextExtent(incText);
753 if (ext.GetWidth() > maxW && maxW > 0) {
754 while (dc.GetTextExtent(incText +
"...").GetWidth() > maxW && incText.Length() > 10) {
755 incText = incText.Left(incText.Length() - 1);
759 dc.DrawText(incText, x + contentWidth - 180, y + 6);
762 y += cardHeaderH + 2;
766 int col1W = (contentWidth - 40) * 45 / 100;
767 int col2W = (contentWidth - 40) * 25 / 100;
768 int col3W = (contentWidth - 40) - col1W - col2W;
771 dc.SetBrush(wxBrush(wxColour(55, 55, 65)));
772 dc.SetPen(wxPen(wxColour(80, 80, 100), 1));
773 dc.DrawRectangle(tableX, y, col1W, rowH);
774 dc.DrawRectangle(tableX + col1W, y, col2W, rowH);
775 dc.DrawRectangle(tableX + col1W + col2W, y, col3W, rowH);
777 dc.SetTextForeground(wxColour(180, 180, 220));
778 dc.SetFont(wxFont(8, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
779 dc.DrawText(
"Key", tableX + 5, y + 3);
780 dc.DrawText(
"Type", tableX + col1W + 5, y + 3);
781 dc.DrawText(
"Value", tableX + col1W + col2W + 5, y + 3);
786 dc.SetFont(wxFont(8, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
787 for (
size_t ei = 0; ei < entries.size(); ++ei) {
788 const auto &e = entries[ei];
791 wxColour rowBg = (ei % 2 == 0) ? wxColour(45, 45, 50) : wxColour(50, 50, 55);
792 dc.SetBrush(wxBrush(rowBg));
793 dc.SetPen(wxPen(wxColour(60, 60, 70), 1));
794 dc.DrawRectangle(tableX, y, col1W, rowH);
795 dc.DrawRectangle(tableX + col1W, y, col2W, rowH);
796 dc.DrawRectangle(tableX + col1W + col2W, y, col3W, rowH);
799 auto truncate = [&dc](
const wxString &text,
int maxWidth) -> wxString {
800 wxString result = text;
801 if (dc.GetTextExtent(result).GetWidth() > maxWidth && maxWidth > 0) {
802 while (dc.GetTextExtent(result +
"...").GetWidth() > maxWidth && result.Length() > 1) {
803 result = result.Left(result.Length() - 1);
810 dc.SetTextForeground(wxColour(200, 200, 200));
811 dc.DrawText(truncate(wxString::FromUTF8(e.key), col1W - 10), tableX + 5, y + 3);
813 dc.SetTextForeground(wxColour(150, 200, 180));
814 dc.DrawText(truncate(wxString::FromUTF8(e.type), col2W - 10), tableX + col1W + 5, y + 3);
816 dc.SetTextForeground(wxColour(180, 180, 150));
817 dc.DrawText(truncate(wxString::FromUTF8(e.value), col3W - 10), tableX + col1W + col2W + 5, y + 3);
831 dc.SetTextForeground(wxColour(150, 150, 150));
832 dc.SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_NORMAL));
833 dc.DrawText(
"No blackboards found in project files", 20, y);
1422 dc.SetBackground(wxBrush(wxColour(45, 45, 45)));
1430 dc.SetTextForeground(wxColour(150, 150, 150));
1431 wxFont font(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1433 dc.DrawText(
"Click 'Validate' to see structure", 20, 20);
1438 std::map<std::string, std::vector<std::string>> treesByFile;
1440 const auto &status = pair.second;
1441 if (status.is_implemented) {
1442 treesByFile[status.defined_in_file].push_back(status.tree_id);
1449 if (rs.has_blackboards && treesByFile.find(rs.filepath) == treesByFile.end()) {
1450 treesByFile[rs.filepath] = {};
1455 int fileBoxWidth = 250;
1456 int fileBoxHeight = 40;
1457 int treeBoxWidth = 200;
1458 int treeBoxHeight = 35;
1460 int leftMargin = 30;
1463 wxFont titleFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
1464 wxFont normalFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1466 int currentY = topMargin;
1477 std::map<std::string, std::vector<std::string>> fileToUnimplementedRefs;
1481 for (
const auto &ref : resource.subtree_refs) {
1483 fileToUnimplementedRefs[resource.filepath].push_back(unimp);
1492 dc.SetFont(titleFont);
1493 dc.SetTextForeground(wxColour(255, 100, 100));
1494 dc.DrawText(
"❌ Project Errors:", leftMargin, currentY);
1498 int errorX = leftMargin + 20;
1501 dc.SetBrush(wxBrush(wxColour(80, 40, 40)));
1502 dc.SetPen(wxPen(wxColour(255, 100, 100), 2));
1503 dc.DrawRoundedRectangle(errorX, currentY, treeBoxWidth, treeBoxHeight, 5);
1506 dc.SetBrush(wxBrush(wxColour(255, 100, 100)));
1507 dc.SetPen(wxPen(wxColour(200, 80, 80), 2));
1508 dc.DrawLine(errorX + 10, currentY + 10, errorX + 20, currentY + 20);
1509 dc.DrawLine(errorX + 20, currentY + 10, errorX + 10, currentY + 20);
1512 dc.SetFont(normalFont);
1513 dc.SetTextForeground(wxColour(255, 150, 150));
1514 wxString errorText = wxString::FromUTF8(error);
1515 int maxTextWidth = treeBoxWidth - 40;
1516 wxSize textExtent = dc.GetTextExtent(errorText);
1517 if (textExtent.GetWidth() > maxTextWidth) {
1518 while (!errorText.IsEmpty() && dc.GetTextExtent(errorText +
"...").GetWidth() > maxTextWidth) {
1519 errorText = errorText.Left(errorText.Length() - 1);
1523 dc.DrawText(errorText, errorX + 30, currentY + 10);
1528 item.
rect = wxRect(errorX, currentY, treeBoxWidth, treeBoxHeight);
1537 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1538 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1539 dc.DrawRoundedRectangle(errorX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
1542 currentY += treeBoxHeight + 10;
1544 currentY += spacing;
1549 dc.SetFont(titleFont);
1550 dc.SetTextForeground(wxColour(255, 150, 100));
1551 dc.DrawText(
"🔄 Circular Dependencies:", leftMargin, currentY);
1555 int circX = leftMargin + 20;
1558 dc.SetBrush(wxBrush(wxColour(80, 50, 40)));
1559 dc.SetPen(wxPen(wxColour(255, 150, 100), 2));
1560 dc.DrawRoundedRectangle(circX, currentY, treeBoxWidth, treeBoxHeight, 5);
1563 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1564 dc.SetPen(wxPen(wxColour(255, 150, 100), 2));
1565 dc.DrawCircle(circX + 15, currentY + 17, 8);
1566 dc.SetBrush(wxBrush(wxColour(255, 150, 100)));
1567 wxPoint circArrow[3] = {wxPoint(circX + 23, currentY + 14), wxPoint(circX + 23, currentY + 20),
1568 wxPoint(circX + 27, currentY + 17)};
1569 dc.DrawPolygon(3, circArrow);
1572 dc.SetFont(normalFont);
1573 dc.SetTextForeground(wxColour(255, 180, 120));
1574 wxString circText = wxString::FromUTF8(circular);
1575 int maxTextWidth = treeBoxWidth - 40;
1576 wxSize textExtent = dc.GetTextExtent(circText);
1577 if (textExtent.GetWidth() > maxTextWidth) {
1578 while (!circText.IsEmpty() && dc.GetTextExtent(circText +
"...").GetWidth() > maxTextWidth) {
1579 circText = circText.Left(circText.Length() - 1);
1583 dc.DrawText(circText, circX + 30, currentY + 10);
1588 item.
rect = wxRect(circX, currentY, treeBoxWidth, treeBoxHeight);
1597 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1598 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1599 dc.DrawRoundedRectangle(circX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
1602 currentY += treeBoxHeight + 10;
1604 currentY += spacing;
1611 dc.SetFont(titleFont);
1612 dc.SetTextForeground(wxColour(255, 200, 100));
1613 dc.DrawText(
"Unimplemented References:", leftMargin, currentY);
1618 int treeX = leftMargin + 20;
1621 dc.SetBrush(wxBrush(wxColour(80, 60, 40)));
1622 dc.SetPen(wxPen(wxColour(255, 180, 0), 2));
1623 dc.DrawRoundedRectangle(treeX, currentY, treeBoxWidth, treeBoxHeight, 5);
1626 dc.SetBrush(wxBrush(wxColour(255, 180, 0)));
1627 dc.SetPen(wxPen(wxColour(200, 140, 0), 1));
1628 wxPoint warnIcon[3] = {wxPoint(treeX + 15, currentY + 10), wxPoint(treeX + 10, currentY + 25),
1629 wxPoint(treeX + 20, currentY + 25)};
1630 dc.DrawPolygon(3, warnIcon);
1631 dc.SetTextForeground(wxColour(50, 30, 0));
1632 dc.DrawText(
"!", treeX + 13, currentY + 11);
1635 dc.SetFont(normalFont);
1636 dc.SetTextForeground(wxColour(255, 200, 100));
1637 wxString treeIdText = wxString::FromUTF8(treeId);
1640 int maxTextWidth = treeBoxWidth - 40;
1641 wxSize textExtent = dc.GetTextExtent(treeIdText);
1642 if (textExtent.GetWidth() > maxTextWidth) {
1644 while (!treeIdText.IsEmpty() && dc.GetTextExtent(treeIdText +
"...").GetWidth() > maxTextWidth) {
1645 treeIdText = treeIdText.Left(treeIdText.Length() - 1);
1647 treeIdText +=
"...";
1650 dc.DrawText(treeIdText, treeX + 30, currentY + 10);
1653 std::string referenceFile;
1655 for (
const auto &ref : resource.subtree_refs) {
1656 if (ref == treeId) {
1657 referenceFile = resource.filepath;
1661 if (!referenceFile.empty())
1668 item.
rect = wxRect(treeX, currentY, treeBoxWidth, treeBoxHeight);
1678 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1679 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1680 dc.DrawRoundedRectangle(treeX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
1683 currentY += treeBoxHeight + 10;
1686 currentY += spacing * 2;
1690 std::set<std::string> filesWithTreesOrErrors;
1693 bool hasFilesWithIssuesButNoTrees =
false;
1694 if (showFiles || showErrors || showWarnings) {
1696 bool hasIssues = (!resource.errors.empty() && showErrors) || (!resource.warnings.empty() && showWarnings) ||
1697 (!fileToUnimplementedRefs[resource.filepath].empty() && showWarnings);
1698 if (hasIssues && treesByFile.find(resource.filepath) == treesByFile.end()) {
1699 hasFilesWithIssuesButNoTrees =
true;
1706 if (hasFilesWithIssuesButNoTrees) {
1707 dc.SetFont(titleFont);
1708 dc.SetTextForeground(wxColour(255, 150, 100));
1709 dc.DrawText(
"Files with Issues:", leftMargin, currentY);
1715 bool hasVisibleErrors = showErrors && !resource.errors.empty();
1716 bool hasVisibleWarnings =
1717 showWarnings && (!resource.warnings.empty() || !fileToUnimplementedRefs[resource.filepath].empty());
1719 if (hasVisibleErrors || hasVisibleWarnings) {
1721 if (treesByFile.find(resource.filepath) == treesByFile.end()) {
1722 filesWithTreesOrErrors.insert(resource.filepath);
1724 wxFileName fn(wxString::FromUTF8(resource.filepath));
1730 dc.SetBrush(wxBrush(wxColour(70, 70, 90)));
1731 dc.SetPen(wxPen(wxColour(100, 100, 150), 2));
1732 dc.DrawRoundedRectangle(leftMargin, currentY, fileBoxWidth, fileBoxHeight, 5);
1735 dc.SetBrush(wxBrush(wxColour(100, 150, 255)));
1736 dc.SetPen(wxPen(wxColour(80, 120, 200), 1));
1737 dc.DrawRectangle(leftMargin + 10, currentY + 10, 20, 20);
1740 dc.SetFont(titleFont);
1741 dc.SetTextForeground(wxColour(200, 200, 255));
1742 wxString fileName = fn.GetFullName();
1743 int maxFileTextWidth = fileBoxWidth - 50;
1744 textExtent = dc.GetTextExtent(fileName);
1745 if (textExtent.GetWidth() > maxFileTextWidth) {
1746 while (!fileName.IsEmpty() &&
1747 dc.GetTextExtent(fileName +
"...").GetWidth() > maxFileTextWidth) {
1748 fileName = fileName.Left(fileName.Length() - 1);
1752 dc.DrawText(fileName, leftMargin + 40, currentY + 12);
1757 fileItem.
rect = wxRect(leftMargin, currentY, fileBoxWidth, fileBoxHeight);
1759 fileItem.
filepath = resource.filepath;
1766 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1767 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1768 dc.DrawRoundedRectangle(leftMargin - 2, currentY - 2, fileBoxWidth + 4, fileBoxHeight + 4, 5);
1771 currentY += fileBoxHeight + spacing;
1776 for (
const auto &error : resource.errors) {
1777 int errorX = leftMargin + 40;
1779 dc.SetBrush(wxBrush(wxColour(80, 40, 40)));
1780 dc.SetPen(wxPen(wxColour(255, 100, 100), 2));
1781 dc.DrawRoundedRectangle(errorX, currentY, treeBoxWidth, treeBoxHeight, 5);
1783 dc.SetBrush(wxBrush(wxColour(255, 100, 100)));
1784 dc.SetPen(wxPen(wxColour(200, 80, 80), 2));
1785 dc.DrawLine(errorX + 10, currentY + 10, errorX + 20, currentY + 20);
1786 dc.DrawLine(errorX + 20, currentY + 10, errorX + 10, currentY + 20);
1788 dc.SetFont(normalFont);
1789 dc.SetTextForeground(wxColour(255, 150, 150));
1790 wxString errorText = wxString::FromUTF8(error);
1791 int maxTextWidth = treeBoxWidth - 40;
1792 textExtent = dc.GetTextExtent(errorText);
1793 if (textExtent.GetWidth() > maxTextWidth) {
1794 while (!errorText.IsEmpty() &&
1795 dc.GetTextExtent(errorText +
"...").GetWidth() > maxTextWidth) {
1796 errorText = errorText.Left(errorText.Length() - 1);
1800 dc.DrawText(errorText, errorX + 30, currentY + 10);
1804 errorItem.
rect = wxRect(errorX, currentY, treeBoxWidth, treeBoxHeight);
1806 errorItem.
filepath = resource.filepath;
1813 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1814 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1815 dc.DrawRoundedRectangle(errorX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
1818 currentY += treeBoxHeight + 10;
1824 for (
const auto &warning : resource.warnings) {
1825 int warnX = leftMargin + 40;
1827 dc.SetBrush(wxBrush(wxColour(80, 60, 40)));
1828 dc.SetPen(wxPen(wxColour(255, 180, 0), 2));
1829 dc.DrawRoundedRectangle(warnX, currentY, treeBoxWidth, treeBoxHeight, 5);
1831 dc.SetBrush(wxBrush(wxColour(255, 180, 0)));
1832 dc.SetPen(wxPen(wxColour(200, 140, 0), 1));
1833 wxPoint warnIcon[3] = {wxPoint(warnX + 15, currentY + 10), wxPoint(warnX + 10, currentY + 25),
1834 wxPoint(warnX + 20, currentY + 25)};
1835 dc.DrawPolygon(3, warnIcon);
1836 dc.SetTextForeground(wxColour(50, 30, 0));
1837 dc.DrawText(
"!", warnX + 13, currentY + 11);
1839 dc.SetFont(normalFont);
1840 dc.SetTextForeground(wxColour(255, 200, 100));
1841 wxString warnText = wxString::FromUTF8(warning);
1842 int maxTextWidth = treeBoxWidth - 40;
1843 textExtent = dc.GetTextExtent(warnText);
1844 if (textExtent.GetWidth() > maxTextWidth) {
1845 while (!warnText.IsEmpty() &&
1846 dc.GetTextExtent(warnText +
"...").GetWidth() > maxTextWidth) {
1847 warnText = warnText.Left(warnText.Length() - 1);
1851 dc.DrawText(warnText, warnX + 30, currentY + 10);
1855 warnItem.
rect = wxRect(warnX, currentY, treeBoxWidth, treeBoxHeight);
1857 warnItem.
filepath = resource.filepath;
1864 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1865 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1866 dc.DrawRoundedRectangle(warnX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
1869 currentY += treeBoxHeight + 10;
1874 if (showWarnings && fileToUnimplementedRefs.count(resource.filepath) > 0) {
1875 for (
const auto &unimpRef : fileToUnimplementedRefs[resource.filepath]) {
1876 int warnX = leftMargin + 40;
1878 dc.SetBrush(wxBrush(wxColour(80, 60, 40)));
1879 dc.SetPen(wxPen(wxColour(255, 180, 0), 2));
1880 dc.DrawRoundedRectangle(warnX, currentY, treeBoxWidth, treeBoxHeight, 5);
1882 dc.SetBrush(wxBrush(wxColour(255, 180, 0)));
1883 dc.SetPen(wxPen(wxColour(200, 140, 0), 1));
1884 wxPoint warnIcon[3] = {wxPoint(warnX + 15, currentY + 10), wxPoint(warnX + 10, currentY + 25),
1885 wxPoint(warnX + 20, currentY + 25)};
1886 dc.DrawPolygon(3, warnIcon);
1887 dc.SetTextForeground(wxColour(50, 30, 0));
1888 dc.DrawText(
"!", warnX + 13, currentY + 11);
1890 dc.SetFont(normalFont);
1891 dc.SetTextForeground(wxColour(255, 200, 100));
1892 wxString warnText =
"Missing reference: " + wxString::FromUTF8(unimpRef);
1893 int maxTextWidth = treeBoxWidth - 40;
1894 textExtent = dc.GetTextExtent(warnText);
1895 if (textExtent.GetWidth() > maxTextWidth) {
1896 while (!warnText.IsEmpty() &&
1897 dc.GetTextExtent(warnText +
"...").GetWidth() > maxTextWidth) {
1898 warnText = warnText.Left(warnText.Length() - 1);
1902 dc.DrawText(warnText, warnX + 30, currentY + 10);
1906 warnItem.
rect = wxRect(warnX, currentY, treeBoxWidth, treeBoxHeight);
1908 warnItem.
filepath = resource.filepath;
1915 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1916 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
1917 dc.DrawRoundedRectangle(warnX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
1920 currentY += treeBoxHeight + 10;
1924 currentY += spacing;
1930 if ((showFiles || showTrees) && !treesByFile.empty()) {
1931 dc.SetFont(titleFont);
1932 dc.SetTextForeground(wxColour(150, 200, 255));
1933 dc.DrawText(
"Implemented Trees:", leftMargin, currentY);
1938 for (
const auto &pair : treesByFile) {
1942 if (resource.filepath == pair.first) {
1943 resourceStatus = &resource;
1948 bool hasVisibleErrors = showErrors && resourceStatus && !resourceStatus->
errors.empty();
1949 bool hasVisibleWarnings = showWarnings && resourceStatus &&
1950 (!resourceStatus->
warnings.empty() || !fileToUnimplementedRefs[pair.first].empty());
1951 bool hasVisibleTrees = showTrees && !pair.second.empty();
1952 bool hasVisibleBlackboards = showBlackboards && resourceStatus && !resourceStatus->
blackboard_ids.empty();
1955 if (!showFiles && !hasVisibleErrors && !hasVisibleWarnings && !hasVisibleTrees && !hasVisibleBlackboards) {
1958 wxFileName fn(wxString::FromUTF8(pair.first));
1964 dc.SetBrush(wxBrush(wxColour(70, 70, 90)));
1965 dc.SetPen(wxPen(wxColour(100, 100, 150), 2));
1966 dc.DrawRoundedRectangle(leftMargin, currentY, fileBoxWidth, fileBoxHeight, 5);
1969 dc.SetBrush(wxBrush(wxColour(100, 150, 255)));
1970 dc.SetPen(wxPen(wxColour(80, 120, 200), 1));
1971 dc.DrawRectangle(leftMargin + 10, currentY + 10, 20, 20);
1974 dc.SetFont(titleFont);
1975 dc.SetTextForeground(wxColour(200, 200, 255));
1976 wxString fileName = fn.GetFullName();
1979 int maxFileTextWidth = fileBoxWidth - 50;
1980 textExtent = dc.GetTextExtent(fileName);
1981 if (textExtent.GetWidth() > maxFileTextWidth) {
1983 while (!fileName.IsEmpty() && dc.GetTextExtent(fileName +
"...").GetWidth() > maxFileTextWidth) {
1984 fileName = fileName.Left(fileName.Length() - 1);
1989 dc.DrawText(fileName, leftMargin + 40, currentY + 12);
1994 fileItem.
rect = wxRect(leftMargin, currentY, fileBoxWidth, fileBoxHeight);
2004 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2005 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
2006 dc.DrawRoundedRectangle(leftMargin - 2, currentY - 2, fileBoxWidth + 4, fileBoxHeight + 4, 5);
2009 currentY += fileBoxHeight + spacing;
2013 if (showErrors && resourceStatus && !resourceStatus->
errors.empty()) {
2014 for (
const auto &error : resourceStatus->
errors) {
2015 int errorX = leftMargin + 40;
2018 dc.SetBrush(wxBrush(wxColour(80, 40, 40)));
2019 dc.SetPen(wxPen(wxColour(255, 100, 100), 2));
2020 dc.DrawRoundedRectangle(errorX, currentY, treeBoxWidth, treeBoxHeight, 5);
2023 dc.SetBrush(wxBrush(wxColour(255, 100, 100)));
2024 dc.SetPen(wxPen(wxColour(200, 80, 80), 2));
2025 dc.DrawLine(errorX + 10, currentY + 10, errorX + 20, currentY + 20);
2026 dc.DrawLine(errorX + 20, currentY + 10, errorX + 10, currentY + 20);
2029 dc.SetFont(normalFont);
2030 dc.SetTextForeground(wxColour(255, 150, 150));
2031 wxString errorText = wxString::FromUTF8(error);
2032 int maxTextWidth = treeBoxWidth - 40;
2033 wxSize textExtent = dc.GetTextExtent(errorText);
2034 if (textExtent.GetWidth() > maxTextWidth) {
2035 while (!errorText.IsEmpty() && dc.GetTextExtent(errorText +
"...").GetWidth() > maxTextWidth) {
2036 errorText = errorText.Left(errorText.Length() - 1);
2040 dc.DrawText(errorText, errorX + 30, currentY + 10);
2045 errorItem.
rect = wxRect(errorX, currentY, treeBoxWidth, treeBoxHeight);
2054 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2055 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
2056 dc.DrawRoundedRectangle(errorX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
2059 currentY += treeBoxHeight + 10;
2064 if (showWarnings && resourceStatus && !resourceStatus->
warnings.empty()) {
2065 for (
const auto &warning : resourceStatus->
warnings) {
2066 int warnX = leftMargin + 40;
2069 dc.SetBrush(wxBrush(wxColour(80, 60, 40)));
2070 dc.SetPen(wxPen(wxColour(255, 180, 0), 2));
2071 dc.DrawRoundedRectangle(warnX, currentY, treeBoxWidth, treeBoxHeight, 5);
2074 dc.SetBrush(wxBrush(wxColour(255, 180, 0)));
2075 dc.SetPen(wxPen(wxColour(200, 140, 0), 1));
2076 wxPoint warnIcon[3] = {wxPoint(warnX + 15, currentY + 10), wxPoint(warnX + 10, currentY + 25),
2077 wxPoint(warnX + 20, currentY + 25)};
2078 dc.DrawPolygon(3, warnIcon);
2079 dc.SetTextForeground(wxColour(50, 30, 0));
2080 dc.DrawText(
"!", warnX + 13, currentY + 11);
2083 dc.SetFont(normalFont);
2084 dc.SetTextForeground(wxColour(255, 200, 100));
2085 wxString warnText = wxString::FromUTF8(warning);
2086 int maxTextWidth = treeBoxWidth - 40;
2087 wxSize textExtent = dc.GetTextExtent(warnText);
2088 if (textExtent.GetWidth() > maxTextWidth) {
2089 while (!warnText.IsEmpty() && dc.GetTextExtent(warnText +
"...").GetWidth() > maxTextWidth) {
2090 warnText = warnText.Left(warnText.Length() - 1);
2094 dc.DrawText(warnText, warnX + 30, currentY + 10);
2099 warnItem.
rect = wxRect(warnX, currentY, treeBoxWidth, treeBoxHeight);
2108 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2109 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
2110 dc.DrawRoundedRectangle(warnX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
2113 currentY += treeBoxHeight + 10;
2118 if (showWarnings && fileToUnimplementedRefs.count(pair.first) > 0) {
2119 for (
const auto &unimpRef : fileToUnimplementedRefs[pair.first]) {
2120 int warnX = leftMargin + 40;
2122 dc.SetBrush(wxBrush(wxColour(80, 60, 40)));
2123 dc.SetPen(wxPen(wxColour(255, 180, 0), 2));
2124 dc.DrawRoundedRectangle(warnX, currentY, treeBoxWidth, treeBoxHeight, 5);
2126 dc.SetBrush(wxBrush(wxColour(255, 180, 0)));
2127 dc.SetPen(wxPen(wxColour(200, 140, 0), 1));
2128 wxPoint warnIcon[3] = {wxPoint(warnX + 15, currentY + 10), wxPoint(warnX + 10, currentY + 25),
2129 wxPoint(warnX + 20, currentY + 25)};
2130 dc.DrawPolygon(3, warnIcon);
2131 dc.SetTextForeground(wxColour(50, 30, 0));
2132 dc.DrawText(
"!", warnX + 13, currentY + 11);
2134 dc.SetFont(normalFont);
2135 dc.SetTextForeground(wxColour(255, 200, 100));
2136 wxString warnText =
"Missing reference: " + wxString::FromUTF8(unimpRef);
2137 int maxTextWidth = treeBoxWidth - 40;
2138 wxSize textExtent = dc.GetTextExtent(warnText);
2139 if (textExtent.GetWidth() > maxTextWidth) {
2140 while (!warnText.IsEmpty() && dc.GetTextExtent(warnText +
"...").GetWidth() > maxTextWidth) {
2141 warnText = warnText.Left(warnText.Length() - 1);
2145 dc.DrawText(warnText, warnX + 30, currentY + 10);
2149 warnItem.
rect = wxRect(warnX, currentY, treeBoxWidth, treeBoxHeight);
2158 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2159 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
2160 dc.DrawRoundedRectangle(warnX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
2163 currentY += treeBoxHeight + 10;
2169 for (
const auto &treeId : pair.second) {
2171 int treeX = leftMargin + 40;
2174 dc.SetBrush(wxBrush(wxColour(60, 80, 60)));
2175 dc.SetPen(wxPen(wxColour(100, 180, 100), 2));
2176 dc.DrawRoundedRectangle(treeX, currentY, treeBoxWidth, treeBoxHeight, 5);
2179 dc.SetBrush(wxBrush(wxColour(80, 200, 100)));
2180 dc.SetPen(wxPen(wxColour(60, 160, 80), 1));
2181 wxPoint treeIcon[3] = {wxPoint(treeX + 15, currentY + 10), wxPoint(treeX + 10, currentY + 25),
2182 wxPoint(treeX + 20, currentY + 25)};
2183 dc.DrawPolygon(3, treeIcon);
2186 dc.SetFont(normalFont);
2187 dc.SetTextForeground(wxColour(150, 255, 150));
2188 wxString treeIdText = wxString::FromUTF8(treeId);
2191 int maxTextWidth = treeBoxWidth - 40;
2192 wxSize textExtent = dc.GetTextExtent(treeIdText);
2193 if (textExtent.GetWidth() > maxTextWidth) {
2195 while (!treeIdText.IsEmpty() && dc.GetTextExtent(treeIdText +
"...").GetWidth() > maxTextWidth) {
2196 treeIdText = treeIdText.Left(treeIdText.Length() - 1);
2198 treeIdText +=
"...";
2201 dc.DrawText(treeIdText, treeX + 30, currentY + 10);
2206 treeItem.
rect = wxRect(treeX, currentY, treeBoxWidth, treeBoxHeight);
2216 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2217 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
2218 dc.DrawRoundedRectangle(treeX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
2221 currentY += treeBoxHeight + 10;
2226 if (showBlackboards && resourceStatus && !resourceStatus->
blackboard_ids.empty()) {
2228 int bbX = leftMargin + 40;
2231 dc.SetBrush(wxBrush(wxColour(70, 50, 80)));
2232 dc.SetPen(wxPen(wxColour(160, 100, 200), 2));
2233 dc.DrawRoundedRectangle(bbX, currentY, treeBoxWidth, treeBoxHeight, 5);
2236 dc.SetBrush(wxBrush(wxColour(180, 120, 230)));
2237 dc.SetPen(wxPen(wxColour(140, 90, 190), 1));
2238 dc.DrawRectangle(bbX + 10, currentY + 10, 16, 16);
2239 dc.DrawLine(bbX + 10, currentY + 18, bbX + 26, currentY + 18);
2240 dc.DrawLine(bbX + 18, currentY + 10, bbX + 18, currentY + 26);
2243 dc.SetFont(normalFont);
2244 dc.SetTextForeground(wxColour(200, 160, 255));
2245 wxString bbText = wxString::FromUTF8(bbId);
2247 int maxTextWidth = treeBoxWidth - 40;
2248 wxSize bbTextExtent = dc.GetTextExtent(bbText);
2249 if (bbTextExtent.GetWidth() > maxTextWidth) {
2250 while (!bbText.IsEmpty() && dc.GetTextExtent(bbText +
"...").GetWidth() > maxTextWidth) {
2251 bbText = bbText.Left(bbText.Length() - 1);
2256 dc.DrawText(bbText, bbX + 30, currentY + 10);
2261 bbItem.
rect = wxRect(bbX, currentY, treeBoxWidth, treeBoxHeight);
2271 dc.SetBrush(*wxTRANSPARENT_BRUSH);
2272 dc.SetPen(wxPen(wxColour(0, 255, 255), 4));
2273 dc.DrawRoundedRectangle(bbX - 2, currentY - 2, treeBoxWidth + 4, treeBoxHeight + 4, 5);
2276 currentY += treeBoxHeight + 10;
2280 currentY += spacing;
2412 int scrollX, scrollY;
2417 wxPoint clickPos =
event.GetPosition();
2418 clickPos.y += scrollY * scrollUnitY;
2423 if (item.rect.Contains(clickPos)) {
2431 if (scrollUnitY > 0) {
2433 int viewStartX, viewStartY;
2435 int visibleTop = viewStartY * scrollUnitY;
2436 int visibleBottom = visibleTop + clientSize.GetHeight();
2439 int topThreshold = visibleTop + clientSize.GetHeight() / 5;
2440 int bottomThreshold = visibleBottom - clientSize.GetHeight() / 5;
2442 if (item.rect.GetTop() < topThreshold || item.rect.GetBottom() > bottomThreshold) {
2444 int targetY = item.rect.GetTop() - (clientSize.GetHeight() / 2) + (item.rect.GetHeight() / 2);
2453 switch (item.type) {
2475 wxString warningDetails;
2478 bool isUnimplementedRef =
false;
2479 bool isCircularRef =
false;
2480 bool isProjectError =
false;
2484 if (unimp == item.identifier) {
2485 isUnimplementedRef =
true;
2491 if (!isUnimplementedRef) {
2493 if (circ == item.identifier) {
2494 isCircularRef =
true;
2501 if (!isUnimplementedRef && !isCircularRef) {
2503 if (err == item.identifier) {
2504 isProjectError =
true;
2511 bool isBlackboardInclude =
false;
2512 bool isBlackboardDuplicate =
false;
2513 bool isBlackboardValidation =
false;
2514 std::string bbIncludeName;
2516 if (item.identifier.find(
"Missing blackboard include: ") == 0) {
2517 isBlackboardInclude =
true;
2518 bbIncludeName = item.identifier.substr(28);
2519 }
else if (item.identifier.find(
"Duplicate blackboard ID") != std::string::npos) {
2520 isBlackboardDuplicate =
true;
2521 }
else if (item.identifier.find(
"Blackboard") != std::string::npos &&
2522 (item.identifier.find(
"missing") != std::string::npos ||
2523 item.identifier.find(
"duplicate entry key") != std::string::npos ||
2524 item.identifier.find(
"unsupported type") != std::string::npos ||
2525 item.identifier.find(
"malformed includes") != std::string::npos)) {
2526 isBlackboardValidation =
true;
2529 if (isUnimplementedRef) {
2530 warningDetails <<
"UNIMPLEMENTED REFERENCE WARNING\n\n";
2531 warningDetails <<
"Issue: SubTree reference not found\n";
2532 warningDetails <<
"Tree ID: " << item.identifier <<
"\n\n";
2533 }
else if (isCircularRef) {
2534 warningDetails <<
"CIRCULAR DEPENDENCY ERROR\n\n";
2535 warningDetails <<
"Issue: Circular reference detected\n";
2536 warningDetails <<
"Chain: " << item.identifier <<
"\n\n";
2537 }
else if (isProjectError) {
2538 warningDetails <<
"PROJECT ERROR\n\n";
2539 warningDetails <<
"Issue: " << item.identifier <<
"\n\n";
2540 }
else if (isBlackboardInclude) {
2541 warningDetails <<
"MISSING BLACKBOARD INCLUDE\n\n";
2542 warningDetails <<
"Issue: Blackboard includes reference not found\n";
2543 warningDetails <<
"Missing ID: " << bbIncludeName <<
"\n\n";
2544 }
else if (isBlackboardDuplicate) {
2545 warningDetails <<
"DUPLICATE BLACKBOARD ID\n\n";
2546 warningDetails <<
"Issue: " << item.identifier <<
"\n\n";
2547 }
else if (isBlackboardValidation) {
2548 warningDetails <<
"BLACKBOARD VALIDATION ERROR\n\n";
2549 warningDetails <<
"Issue: " << item.identifier <<
"\n\n";
2552 bool isError = item.identifier.find(
"Error") != std::string::npos ||
2553 item.identifier.find(
"error") != std::string::npos ||
2554 item.identifier.find(
"ERROR") != std::string::npos;
2557 warningDetails <<
"FILE ERROR\n\n";
2559 warningDetails <<
"FILE WARNING\n\n";
2561 warningDetails <<
"Issue: " << item.identifier <<
"\n\n";
2564 if (!item.filepath.empty()) {
2565 wxFileName fn(wxString::FromUTF8(item.filepath));
2566 warningDetails <<
"File: " << fn.GetFullName() <<
"\n";
2567 warningDetails <<
"Path: " << item.filepath <<
"\n\n";
2569 if (isUnimplementedRef) {
2570 warningDetails <<
"Description: The file references a SubTree with ID \"" << item.identifier
2573 <<
"but no BehaviorTree with this ID exists in any of the project's XML files. ";
2574 warningDetails <<
"This will cause a runtime error if this tree is executed.\n\n";
2575 warningDetails <<
"Solution: Create a BehaviorTree with ID=\"" << item.identifier <<
"\" ";
2576 warningDetails <<
"in one of your XML files, or fix the SubTree reference if it's a typo.";
2577 }
else if (isCircularRef) {
2578 warningDetails <<
"Description: This represents a circular dependency chain where trees "
2579 "reference each other ";
2580 warningDetails <<
"in a loop. This will cause infinite recursion at runtime.\n\n";
2581 warningDetails <<
"Solution: Review the SubTree references in your files and break the "
2582 "circular dependency ";
2583 warningDetails <<
"by restructuring your tree hierarchy.";
2584 }
else if (isBlackboardInclude) {
2586 <<
"Description: A Blackboard in this file uses includes=\"{...}\" referencing \""
2587 << bbIncludeName <<
"\" ";
2589 <<
"but no Blackboard with this ID exists in any of the project's XML files. ";
2590 warningDetails <<
"At runtime, the blackboard will be missing the included entries.\n\n";
2591 warningDetails <<
"Solution: Create a Blackboard with ID=\"" << bbIncludeName <<
"\" ";
2592 warningDetails <<
"in one of your XML files, or fix the includes reference if it's a typo.";
2593 }
else if (isBlackboardDuplicate) {
2595 <<
"Description: Multiple Blackboards share the same ID across the project. ";
2596 warningDetails <<
"This can cause unpredictable behavior as one definition may override "
2598 warningDetails <<
"Solution: Rename one of the duplicate Blackboard IDs to be unique, or "
2599 "merge them into a single definition.";
2600 }
else if (isBlackboardValidation) {
2601 if (item.identifier.find(
"missing required") != std::string::npos) {
2602 warningDetails <<
"Description: A Blackboard or Entry element is missing a required "
2604 warningDetails <<
"All Blackboards must have an 'ID' attribute, and all Entries must "
2605 "have 'key' and 'type' attributes.\n\n";
2606 warningDetails <<
"Solution: Add the missing attribute to the XML element. Check the "
2607 "syntax carefully.";
2608 }
else if (item.identifier.find(
"duplicate entry key") != std::string::npos) {
2610 <<
"Description: A Blackboard has multiple entries with the same key name. ";
2611 warningDetails <<
"This can cause one entry to overwrite another.\n\n";
2612 warningDetails <<
"Solution: Rename one of the duplicate entry keys to be unique, or "
2613 "remove the duplicate.";
2614 }
else if (item.identifier.find(
"unsupported type") != std::string::npos) {
2615 warningDetails <<
"Description: A Blackboard entry uses a data type that is not "
2616 "recognized by the parser. ";
2617 warningDetails <<
"This could be a typo or an unsupported type.\n\n";
2619 <<
"Solution: Check the type name for typos (e.g., 'strng' should be 'string'). ";
2620 warningDetails <<
"Supported types include: bool, int, float, string, vec(...), "
2621 "map(...), set(...), and custom types starting with uppercase.";
2622 }
else if (item.identifier.find(
"malformed includes") != std::string::npos) {
2623 warningDetails <<
"Description: A Blackboard's 'includes' attribute doesn't follow the "
2625 warningDetails <<
"The includes must be in the format: includes=\"{ID1 ID2 ID3}\" with "
2627 warningDetails <<
"Solution: Fix the includes syntax to use curly braces around the "
2628 "IDs: includes=\"{BLACKBOARD_ID1 BLACKBOARD_ID2}\".";
2630 warningDetails <<
"Description: A blackboard validation issue was detected.\n\n";
2631 warningDetails <<
"Solution: Review the blackboard definition and fix any syntax or "
2632 "structural issues.";
2635 warningDetails <<
"Description: This file has a validation issue that needs attention. ";
2636 warningDetails <<
"Review the file content and fix any syntax errors, missing attributes, "
2637 "or structural problems.\n\n";
2638 warningDetails <<
"Solution: Open the file and examine the highlighted area for issues. ";
2639 warningDetails <<
"Common problems include malformed XML, missing required attributes, or "
2640 "invalid node types.";
2648 std::ifstream file(item.filepath);
2649 if (file.is_open()) {
2650 std::string content((std::istreambuf_iterator<char>(file)),
2651 std::istreambuf_iterator<char>());
2654 int lineNumber = -1;
2656 if (isUnimplementedRef) {
2658 std::string searchStr =
"SubTree ID=\"" + item.identifier +
"\"";
2659 size_t pos = content.find(searchStr);
2661 if (pos == std::string::npos) {
2663 searchStr =
"SubTree ID='" + item.identifier +
"'";
2664 pos = content.find(searchStr);
2667 if (pos != std::string::npos) {
2670 for (
size_t i = 0; i < pos; ++i) {
2671 if (content[i] ==
'\n') {
2676 }
else if (isBlackboardInclude && !bbIncludeName.empty()) {
2678 size_t pos = content.find(bbIncludeName);
2679 if (pos != std::string::npos) {
2682 for (
size_t i = 0; i < pos; ++i) {
2683 if (content[i] ==
'\n') {
2688 }
else if (isBlackboardDuplicate) {
2690 std::string dupId = item.identifier;
2692 size_t colonPos = dupId.rfind(
": ");
2693 if (colonPos != std::string::npos) {
2694 std::string bbId = dupId.substr(colonPos + 2);
2695 std::string searchStr =
"Blackboard ID=\"" + bbId +
"\"";
2696 size_t pos = content.find(searchStr);
2697 if (pos == std::string::npos) {
2698 searchStr =
"Blackboard ID='" + bbId +
"'";
2699 pos = content.find(searchStr);
2701 if (pos != std::string::npos) {
2703 for (
size_t i = 0; i < pos; ++i) {
2704 if (content[i] ==
'\n') {
2712 std::string identifier = item.identifier;
2715 if (identifier.find(
"Blackboard element missing") != std::string::npos) {
2717 size_t pos = content.find(
"<Blackboard");
2718 while (pos != std::string::npos) {
2719 size_t lineEnd = content.find(
'>', pos);
2720 if (lineEnd != std::string::npos) {
2721 std::string tag = content.substr(pos, lineEnd - pos + 1);
2722 if (tag.find(
"ID=") == std::string::npos) {
2725 for (
size_t i = 0; i < pos; ++i) {
2726 if (content[i] ==
'\n')
2732 pos = content.find(
"<Blackboard", pos + 1);
2736 else if (identifier.find(
"has Entry missing required 'key'") != std::string::npos) {
2737 size_t quoteStart = identifier.find(
'\'');
2738 size_t quoteEnd = identifier.find(
'\'', quoteStart + 1);
2739 if (quoteStart != std::string::npos && quoteEnd != std::string::npos) {
2740 std::string bbId = identifier.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
2742 std::string searchStr =
"Blackboard ID=\"" + bbId +
"\"";
2743 size_t bbPos = content.find(searchStr);
2744 if (bbPos == std::string::npos) {
2745 searchStr =
"Blackboard ID='" + bbId +
"'";
2746 bbPos = content.find(searchStr);
2748 if (bbPos != std::string::npos) {
2750 size_t entryPos = content.find(
"<Entry", bbPos);
2751 while (entryPos != std::string::npos) {
2752 size_t lineEnd = content.find(
'>', entryPos);
2753 if (lineEnd != std::string::npos) {
2754 std::string tag = content.substr(entryPos, lineEnd - entryPos + 1);
2755 if (tag.find(
"key=") == std::string::npos) {
2757 for (
size_t i = 0; i < entryPos; ++i) {
2758 if (content[i] ==
'\n')
2764 entryPos = content.find(
"<Entry", entryPos + 1);
2766 size_t nextBB = content.find(
"<Blackboard", bbPos + 1);
2767 if (nextBB != std::string::npos && entryPos > nextBB)
2776 else if (identifier.find(
"entry '") != std::string::npos ||
2777 identifier.find(
"entry key: ") != std::string::npos) {
2779 std::string entryKey;
2780 size_t keyStart = identifier.find(
"entry '");
2781 if (keyStart != std::string::npos) {
2783 size_t keyEnd = identifier.find(
'\'', keyStart);
2784 if (keyEnd != std::string::npos) {
2785 entryKey = identifier.substr(keyStart, keyEnd - keyStart);
2788 keyStart = identifier.find(
"entry key: ");
2789 if (keyStart != std::string::npos) {
2790 entryKey = identifier.substr(keyStart + 11);
2794 if (!entryKey.empty()) {
2796 std::string searchStr =
"Entry key=\"" + entryKey +
"\"";
2797 size_t pos = content.find(searchStr);
2798 if (pos == std::string::npos) {
2799 searchStr =
"Entry key='" + entryKey +
"'";
2800 pos = content.find(searchStr);
2802 if (pos != std::string::npos) {
2804 for (
size_t i = 0; i < pos; ++i) {
2805 if (content[i] ==
'\n')
2812 else if (identifier.find(
"has malformed includes syntax") != std::string::npos) {
2813 size_t quoteStart = identifier.find(
'\'');
2814 size_t quoteEnd = identifier.find(
'\'', quoteStart + 1);
2815 if (quoteStart != std::string::npos && quoteEnd != std::string::npos) {
2816 std::string bbId = identifier.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
2817 std::string searchStr =
"Blackboard ID=\"" + bbId +
"\"";
2818 size_t pos = content.find(searchStr);
2819 if (pos == std::string::npos) {
2820 searchStr =
"Blackboard ID='" + bbId +
"'";
2821 pos = content.find(searchStr);
2823 if (pos != std::string::npos) {
2825 for (
size_t i = 0; i < pos; ++i) {
2826 if (content[i] ==
'\n')
2834 if (lineNumber > 0) {
2842 wxString::Format(
"Error: Could not open file '%s'", item.filepath));
2847 if (isUnimplementedRef) {
2848 warningDetails <<
"Location: Unknown file\n\n";
2849 warningDetails <<
"Description: The SubTree ID \"" << item.identifier
2850 <<
"\" is referenced ";
2851 warningDetails <<
"somewhere in the project but no BehaviorTree with this ID exists. ";
2852 warningDetails <<
"The specific file reference could not be determined.\n\n";
2853 warningDetails <<
"Solution: Search your XML files for SubTree nodes with ID=\""
2854 << item.identifier <<
"\" ";
2855 warningDetails <<
"and either implement the tree or fix the reference.";
2856 }
else if (isCircularRef) {
2857 warningDetails <<
"Location: Multiple files\n\n";
2858 warningDetails <<
"Description: A circular dependency has been detected in your project. ";
2859 warningDetails <<
"The dependency chain is: " << item.identifier <<
"\n\n";
2861 <<
"Solution: Review the files involved in this chain and restructure your trees ";
2862 warningDetails <<
"to eliminate the circular reference.";
2863 }
else if (isProjectError) {
2864 warningDetails <<
"Location: Project level\n\n";
2865 warningDetails <<
"Description: This is a project-wide issue that affects the entire "
2866 "project structure.\n\n";
2867 warningDetails <<
"Solution: Review your project configuration and ensure all required "
2868 "settings are valid.";
2870 warningDetails <<
"Location: Unknown\n\n";
2871 warningDetails <<
"Description: A validation issue was detected but its specific location "
2872 "could not be determined.\n\n";
2873 warningDetails <<
"Solution: Review the validation report tab for more details.";
2882 if (isCircularRef) {
2884 "Circular Dependency Detected\n\n"
2886 "This indicates that trees reference each other in a loop, which will cause\n"
2887 "infinite recursion at runtime. Review the files in your project to identify\n"
2888 "and break the circular reference chain.",
2890 }
else if (isProjectError) {
2892 wxString::Format(
"Project Error\n\n"
2894 "This is a project-level error. Review your project settings\n"
2895 "and configuration to resolve this issue.",
2899 wxString::Format(
"Validation Issue\n\n"
2901 "No specific file reference found.\n"
2902 "Check the validation report tab for more details.",