From 30cfbd1f91c613e09ec64a13f69187adcd459ae7 Mon Sep 17 00:00:00 2001 From: Florent Rougon Date: Tue, 27 Feb 2018 01:17:30 +0100 Subject: [PATCH] Add-ons: load add-on-specific menu bar items from addon-menubar-items.xml If an add-on has a file named addon-menubar-items.xml in its base directory, load it and add its items to the FG menubar. Logically, fgStartNewReset() should call flightgear::addons::AddonManager::instance()->addAddonMenusToFGMenubar() in order to re-add the items, however doing so would cause the add-on-specific menus to be added one more time on every reset, because for some reason, commit 45ea8b5daadb1ca5d24cf3c5fff3b4e0905b5ba3 added the PRESERVE attribute to /sim/menubar (apparently to preserve the state of menu entries upon reset?). Note: the addon-menubar-items.xml files are reloaded during reset, however the menu bar doesn't reflect this, since adding the reloaded items to the menu bar in fgStartNewReset() would cause the add-on-specific menus to appear several times in the menu bar, as explained above. --- src/Add-ons/Addon.cxx | 90 ++++++++++++++++++++++++++++++++++++ src/Add-ons/Addon.hxx | 12 +++++ src/Add-ons/AddonManager.cxx | 8 ++++ src/Add-ons/AddonManager.hxx | 4 ++ src/Add-ons/addon_fwd.hxx | 1 + src/Add-ons/exceptions.hxx | 3 ++ src/Main/main.cxx | 3 ++ 7 files changed, 121 insertions(+) diff --git a/src/Add-ons/Addon.cxx b/src/Add-ons/Addon.cxx index ee6686db0..fe7411e81 100644 --- a/src/Add-ons/Addon.cxx +++ b/src/Add-ons/Addon.cxx @@ -29,7 +29,9 @@ #include #include #include +#include +#include
#include
#include @@ -266,6 +268,22 @@ int Addon::getLoadSequenceNumber() const void Addon::setLoadSequenceNumber(int num) { _loadSequenceNumber = num; } +std::vector Addon::getMenubarNodes() const +{ return _menubarNodes; } + +void Addon::setMenubarNodes(const std::vector& menubarNodes) +{ _menubarNodes = menubarNodes; } + +void Addon::addToFGMenubar() const +{ + SGPropertyNode* menuRootNode = fgGetNode("/sim/menubar/default", true); + + for (const auto& node: getMenubarNodes()) { + SGPropertyNode* childNode = menuRootNode->addChild("menu"); + ::copyProperties(node.ptr(), childNode); + } +} + std::string Addon::str() const { std::ostringstream oss; @@ -311,9 +329,81 @@ Addon Addon::fromAddonDir(const SGPath& addonPath) addon.setSupportUrl(std::move(metadata.supportUrl)); addon.setCodeRepositoryUrl(std::move(metadata.codeRepositoryUrl)); + SGPath menuFile = addonPath / "addon-menubar-items.xml"; + + if (menuFile.exists()) { + addon.setMenubarNodes(readMenubarItems(menuFile)); + } + return addon; } +// Static method +std::vector +Addon::readMenubarItems(const SGPath& menuFile) +{ + SGPropertyNode rootNode; + + try { + readProperties(menuFile, &rootNode); + } catch (const sg_exception &e) { + throw errors::error_loading_menubar_items_file( + "unable to load add-on menu bar items from file '" + + menuFile.utf8Str() + "': " + e.getFormattedMessage()); + } + + // Check the 'meta' section + SGPropertyNode *metaNode = rootNode.getChild("meta"); + if (metaNode == nullptr) { + throw errors::error_loading_menubar_items_file( + "no /meta node found in add-on menu bar items file '" + + menuFile.utf8Str() + "'"); + } + + // Check the file type + SGPropertyNode *fileTypeNode = metaNode->getChild("file-type"); + if (fileTypeNode == nullptr) { + throw errors::error_loading_menubar_items_file( + "no /meta/file-type node found in add-on menu bar items file '" + + menuFile.utf8Str() + "'"); + } + + string fileType = fileTypeNode->getStringValue(); + if (fileType != "FlightGear add-on menu bar items") { + throw errors::error_loading_menubar_items_file( + "Invalid /meta/file-type value for add-on menu bar items file '" + + menuFile.utf8Str() + "': '" + fileType + "' " + "(expected 'FlightGear add-on menu bar items')"); + } + + // Check the format version + SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version"); + if (fmtVersionNode == nullptr) { + throw errors::error_loading_menubar_items_file( + "no /meta/format-version node found in add-on menu bar items file '" + + menuFile.utf8Str() + "'"); + } + + int formatVersion = fmtVersionNode->getIntValue(); + if (formatVersion != 1) { + throw errors::error_loading_menubar_items_file( + "unknown format version in add-on menu bar items file '" + + menuFile.utf8Str() + "': " + std::to_string(formatVersion)); + } + + SG_LOG(SG_GENERAL, SG_DEBUG, + "Loaded add-on menu bar items from '" << menuFile.utf8Str() + "'"); + + SGPropertyNode *menubarItemsNode = rootNode.getChild("menubar-items"); + std::vector res; + + if (menubarItemsNode != nullptr) { + res = menubarItemsNode->getChildren("menu"); + } + + return res; +} + // Static method void Addon::setupGhost(nasal::Hash& addonsModule) { diff --git a/src/Add-ons/Addon.hxx b/src/Add-ons/Addon.hxx index 9cb3c5ad6..062216d43 100644 --- a/src/Add-ons/Addon.hxx +++ b/src/Add-ons/Addon.hxx @@ -173,6 +173,12 @@ public: // Get all non-empty URLs pertaining to this add-on std::multimap getUrls() const; + // Getter and setter for the menu bar item nodes of the add-on + std::vector getMenubarNodes() const; + void setMenubarNodes(const std::vector& menubarNodes); + // Add the menus defined in addon-menubar-items.xml to /sim/menubar/default + void addToFGMenubar() const; + // Simple string representation std::string str() const; @@ -186,6 +192,10 @@ private: static SGPath getMetadataFile(const SGPath& addonPath); SGPath getMetadataFile() const; + // Read all menus from addon-menubar-items.xml (under the add-on base path) + static std::vector + readMenubarItems(const SGPath& menuFile); + // The add-on identifier, in reverse DNS style. The AddonManager refuses to // register two add-ons with the same id in a given FlightGear session. std::string _id; @@ -226,6 +236,8 @@ private: std::string _triggerProperty; // Semantics explained above int _loadSequenceNumber = -1; + + std::vector _menubarNodes; }; std::ostream& operator<<(std::ostream& os, const Addon& addon); diff --git a/src/Add-ons/AddonManager.cxx b/src/Add-ons/AddonManager.cxx index 74ab00e49..9faae1ca2 100644 --- a/src/Add-ons/AddonManager.cxx +++ b/src/Add-ons/AddonManager.cxx @@ -255,6 +255,14 @@ SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const return getAddon(addonId)->getAddonNode(); } +void +AddonManager::addAddonMenusToFGMenubar() const +{ + for (const auto& addon: _registeredAddons) { + addon->addToFGMenubar(); + } +} + } // of namespace addons } // of namespace flightgear diff --git a/src/Add-ons/AddonManager.hxx b/src/Add-ons/AddonManager.hxx index 942888b69..53a614c91 100644 --- a/src/Add-ons/AddonManager.hxx +++ b/src/Add-ons/AddonManager.hxx @@ -86,6 +86,10 @@ public: // Base node pertaining to the add-on in the Global Property Tree SGPropertyNode_ptr addonNode(const std::string& addonId) const; + // Add the 'menu' nodes defined by each registered add-on to + // /sim/menubar/default + void addAddonMenusToFGMenubar() const; + private: // Constructor called from createInstance() only explicit AddonManager() = default; diff --git a/src/Add-ons/addon_fwd.hxx b/src/Add-ons/addon_fwd.hxx index 390953282..cb5ef9889 100644 --- a/src/Add-ons/addon_fwd.hxx +++ b/src/Add-ons/addon_fwd.hxx @@ -56,6 +56,7 @@ class error; class error_loading_config_file; class no_metadata_file_found; class error_loading_metadata_file; +class error_loading_menubar_items_file; class duplicate_registration_attempt; class fg_version_too_old; class fg_version_too_recent; diff --git a/src/Add-ons/exceptions.hxx b/src/Add-ons/exceptions.hxx index 08ede87d6..0fb162cf1 100644 --- a/src/Add-ons/exceptions.hxx +++ b/src/Add-ons/exceptions.hxx @@ -50,6 +50,9 @@ class no_metadata_file_found : public error class error_loading_metadata_file : public error { using error::error; }; +class error_loading_menubar_items_file : public error +{ using error::error; }; + class duplicate_registration_attempt : public error { using error::error; }; diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 74a61afc6..40578b8d4 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -585,6 +585,9 @@ int fgMainInit( int argc, char **argv ) SG_LOG(SG_GENERAL, SG_INFO, "EmbeddedResourceManager: selected locale '" << locale << "'"); + // Copy the property nodes for the menus added by registered add-ons + addons::AddonManager::instance()->addAddonMenusToFGMenubar(); + // Initialize the Window/Graphics environment. fgOSInit(&argc, argv); _bootstrap_OSInit++;