diff --git a/src/Add-ons/Addon.cxx b/src/Add-ons/Addon.cxx index ee6686db0..895ed2138 100644 --- a/src/Add-ons/Addon.cxx +++ b/src/Add-ons/Addon.cxx @@ -17,6 +17,7 @@ // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +#include #include #include #include @@ -29,7 +30,9 @@ #include #include #include +#include +#include
#include
#include @@ -266,6 +269,49 @@ int Addon::getLoadSequenceNumber() const void Addon::setLoadSequenceNumber(int num) { _loadSequenceNumber = num; } +std::multimap Addon::getUrls() const +{ + std::multimap res; + + auto appendIfNonEmpty = [&res](UrlType type, string url, string detail = "") { + if (!url.empty()) { + res.emplace(type, QualifiedUrl(type, std::move(url), std::move(detail))); + } + }; + + for (const auto& author: _authors) { + appendIfNonEmpty(UrlType::author, author->getUrl(), author->getName()); + } + + for (const auto& maint: _maintainers) { + appendIfNonEmpty(UrlType::maintainer, maint->getUrl(), maint->getName()); + } + + appendIfNonEmpty(UrlType::homePage, getHomePage()); + appendIfNonEmpty(UrlType::download, getDownloadUrl()); + appendIfNonEmpty(UrlType::support, getSupportUrl()); + appendIfNonEmpty(UrlType::codeRepository, getCodeRepositoryUrl()); + appendIfNonEmpty(UrlType::license, getLicenseUrl()); + + return res; +} + +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 +357,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/GUI/AddCatalogDialog.cxx b/src/GUI/AddCatalogDialog.cxx index b7d91314c..a0bd19b0f 100644 --- a/src/GUI/AddCatalogDialog.cxx +++ b/src/GUI/AddCatalogDialog.cxx @@ -84,7 +84,12 @@ CatalogRef AddCatalogDialog::addedCatalog() void AddCatalogDialog::setNonInteractiveMode() { m_nonInteractiveMode = true; - ui->buttonBox->hide(); + ui->buttonBox->hide(); +} + +void AddCatalogDialog::setUpdatingExistingCatalog() +{ + m_updatingExistingCatalog = true; } void AddCatalogDialog::setUrlAndDownload(QUrl url) @@ -187,10 +192,23 @@ void AddCatalogDialog::accept() void AddCatalogDialog::reject() { - if (m_result && !m_result->id().empty()) { - // user may have successfully download the catalog, but choosen - // not to add it. so remove it here - m_packageRoot->removeCatalogById(m_result->id()); + // user may have successfully download the catalog, but choosen + // not to add it. so remove it here + + // however, if we got in here as part of re-validating an existing + // disabled catalog, *but* the re-validation also failed, we do not + // want to remove it here. + + if (m_result && !m_result->id().empty()) + { + if (m_updatingExistingCatalog) { + qWarning() << "cancelled add, but existing catalog so not removing:" + << QString::fromStdString(m_result->id()); + } else { + qWarning() << "removed cancelled catalog:" + << QString::fromStdString(m_result->id()); + m_packageRoot->removeCatalogById(m_result->id()); + } } QDialog::reject(); diff --git a/src/GUI/AddCatalogDialog.hxx b/src/GUI/AddCatalogDialog.hxx index fcea7313e..da5f2cda1 100644 --- a/src/GUI/AddCatalogDialog.hxx +++ b/src/GUI/AddCatalogDialog.hxx @@ -52,6 +52,13 @@ public: */ void setNonInteractiveMode(); + /** + * @brief setUpdatingExistingCatalog - indicate that this is an update or + * fix of an existing catalog, in which case we will treat failures / + * cancellation differently. + */ + void setUpdatingExistingCatalog(); + void setUrlAndDownload(QUrl url); private slots: virtual void reject(); @@ -82,7 +89,7 @@ private: QUrl m_catalogUrl; simgear::pkg::CatalogRef m_result; bool m_nonInteractiveMode = false; - + bool m_updatingExistingCatalog = false; std::unique_ptr m_delegate; }; diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx index 316d6fc75..241bd774f 100644 --- a/src/GUI/AircraftModel.cxx +++ b/src/GUI/AircraftModel.cxx @@ -67,6 +67,8 @@ protected: qWarning() << "failed refresh of " << QString::fromStdString(aCatalog->url()) << ":" << aReason << endl; } + + m_model->catalogsRefreshed(); } void startInstall(InstallRef aInstall) override diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx index 8cd85c246..84e03b781 100644 --- a/src/GUI/AircraftModel.hxx +++ b/src/GUI/AircraftModel.hxx @@ -121,6 +121,7 @@ signals: void aircraftNeedingUpdatedChanged(); + void catalogsRefreshed(); public slots: void setShowUpdateAll(bool showUpdateAll); diff --git a/src/GUI/CatalogListModel.cxx b/src/GUI/CatalogListModel.cxx index b60f22d28..4d0de86c3 100644 --- a/src/GUI/CatalogListModel.cxx +++ b/src/GUI/CatalogListModel.cxx @@ -33,10 +33,13 @@ // FlightGear #include
+using namespace simgear::pkg; + CatalogListModel::CatalogListModel(QObject* pr, simgear::pkg::RootRef& rootRef) : QAbstractListModel(pr), m_packageRoot(rootRef) { + refresh(); } CatalogListModel::~CatalogListModel() @@ -46,21 +49,39 @@ CatalogListModel::~CatalogListModel() void CatalogListModel::refresh() { beginResetModel(); + m_catalogs = m_packageRoot->allCatalogs(); endResetModel(); } int CatalogListModel::rowCount(const QModelIndex& parent) const { - return m_packageRoot->catalogs().size(); + return m_catalogs.size(); } QVariant CatalogListModel::data(const QModelIndex& index, int role) const { - simgear::pkg::CatalogRef cat = m_packageRoot->catalogs().at(index.row()); + simgear::pkg::CatalogRef cat = m_catalogs.at(index.row()); if (role == Qt::DisplayRole) { QString name = QString::fromStdString(cat->name()); - QString desc = QString::fromStdString(cat->description()).simplified(); + QString desc; + if (cat->isEnabled()) { + desc = QString::fromStdString(cat->description()).simplified(); + } else { + switch (cat->status()) { + case Delegate::FAIL_NOT_FOUND: + desc = tr("The catalog data was not found on the server at the expected location (URL)"); + break; + case Delegate::FAIL_VERSION: + desc = tr("The catalog is not comaptible with the version of FlightGear"); + break; + case Delegate::FAIL_HTTP_FORBIDDEN: + desc = tr("The catalog server is blocking access from some reason (forbidden)"); + break; + default: + desc = tr("disabled due to an internal error"); + } + } return tr("%1 - %2").arg(name).arg(desc); } else if (role == Qt::ToolTipRole) { return QString::fromStdString(cat->url()); @@ -81,3 +102,13 @@ bool CatalogListModel::setData(const QModelIndex &index, const QVariant &value, { return false; } + +Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags r = Qt::ItemIsSelectable; + const auto cat = m_catalogs.at(index.row()); + if (cat->isEnabled()) { + r |= Qt::ItemIsEnabled; + } + return r; +} diff --git a/src/GUI/CatalogListModel.hxx b/src/GUI/CatalogListModel.hxx index 8b3fc4723..9df679f52 100644 --- a/src/GUI/CatalogListModel.hxx +++ b/src/GUI/CatalogListModel.hxx @@ -45,17 +45,20 @@ public: void refresh(); - virtual int rowCount(const QModelIndex& parent) const; + int rowCount(const QModelIndex& parent) const override; - virtual QVariant data(const QModelIndex& index, int role) const; + QVariant data(const QModelIndex& index, int role) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; private slots: private: simgear::pkg::RootRef m_packageRoot; + simgear::pkg::CatalogList m_catalogs; }; #endif // of FG_GUI_CATALOG_LIST_MODEL diff --git a/src/GUI/LauncherMainWindow.cxx b/src/GUI/LauncherMainWindow.cxx index 7766fafe9..a18b44ff2 100644 --- a/src/GUI/LauncherMainWindow.cxx +++ b/src/GUI/LauncherMainWindow.cxx @@ -104,7 +104,6 @@ LauncherMainWindow::LauncherMainWindow() : this, &LauncherMainWindow::onViewCommandLine); m_serversModel = new MPServersModel(this); - m_serversModel->refresh(); // keep the description QLabel in sync as the current item changes connect(m_ui->stateCombo, @@ -151,7 +150,6 @@ LauncherMainWindow::LauncherMainWindow() : m_ui->locationHistory->setIcon(historyIcon); m_aircraftModel = new AircraftItemModel(this); - m_installedAircraftModel = new AircraftProxyModel(this, m_aircraftModel); m_installedAircraftModel->setInstalledFilterEnabled(true); @@ -185,6 +183,9 @@ LauncherMainWindow::LauncherMainWindow() : this, &LauncherMainWindow::onAircraftPathsChanged); m_ui->stack->addWidget(addOnsPage); + connect (m_aircraftModel, &AircraftItemModel::catalogsRefreshed, + addOnsPage, &AddOnsPage::onCatalogsRefreshed); + QSettings settings; LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList()); LocalAircraftCache::instance()->scanDirs(); @@ -1015,6 +1016,11 @@ void LauncherMainWindow::requestUpdateAllAircraft() }); } +void LauncherMainWindow::queryMPServers() +{ + m_serversModel->refresh(); +} + bool LauncherMainWindow::showNoOfficialHanger() const { return shouldShowOfficialCatalogMessage(); diff --git a/src/GUI/LauncherMainWindow.hxx b/src/GUI/LauncherMainWindow.hxx index 3a43aef8b..d21ef850c 100644 --- a/src/GUI/LauncherMainWindow.hxx +++ b/src/GUI/LauncherMainWindow.hxx @@ -84,6 +84,8 @@ public: Q_INVOKABLE void requestUpdateAllAircraft(); + Q_INVOKABLE void queryMPServers(); + bool showNoOfficialHanger() const; Q_INVOKABLE void officialCatalogAction(QString s); diff --git a/src/GUI/MPSettings.qml b/src/GUI/MPSettings.qml index 7c3c98a47..b9e6a68cc 100644 --- a/src/GUI/MPSettings.qml +++ b/src/GUI/MPSettings.qml @@ -13,6 +13,13 @@ Section { + "flight. This requires a moderately fast Internet connection to be usable. Your aircraft " + "will be visible to other users online, and you will see their aircraft.") keywords: ["network", "mp", "multiplay","online"] + + onCheckedChanged: { + if (checked) { + // kick off a server query now + _launcher.queryMPServers(); + } + } } LineEdit { @@ -100,7 +107,12 @@ Section { } onRestore: { - // nothing to do, restoration is done by the C++ code + if (enableMP.checked) { + // only query servers if MP is enabled + _launcher.queryMPServers(); + } + + // restoration is done by the C++ code // in MPServersModel::restoreMPServerSelection } diff --git a/src/GUI/MapWidget.cxx b/src/GUI/MapWidget.cxx index e28df0e21..f636e5441 100644 --- a/src/GUI/MapWidget.cxx +++ b/src/GUI/MapWidget.cxx @@ -787,7 +787,7 @@ void MapWidget::paintRuler() SGVec2d acftPos = project(_aircraft); SGVec2d clickPos = project(_clickGeod); - glColor4f(0.0, 1.0, 1.0, 0.6); + glColor3f(0.0, 1.0, 1.0); drawLine(acftPos, clickPos); circleAtAlt(clickPos, 8, 10, 5); @@ -812,7 +812,7 @@ void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos) double hdg = fgGetDouble("/orientation/heading-deg"); glLineWidth(2.0); - glColor4f(1.0, 1.0, 0.0, 1.0); + glColor3f(1.0, 1.0, 0.0); glPushMatrix(); glTranslated(loc.x(), loc.y(), 0.0); glRotatef(hdg - _upHeading, 0.0, 0.0, -1.0); @@ -848,9 +848,9 @@ void MapWidget::paintRoute() } if (w < _route->currentIndex()) { - glColor4f(0.5, 0.5, 0.5, 0.7); + glColor3f(0.5, 0.5, 0.5); } else { - glColor4f(1.0, 0.0, 1.0, 1.0); + glColor3f(1.0, 0.0, 1.0); } flightgear::WayptRef wpt(_route->wayptAtIndex(w)); @@ -879,7 +879,7 @@ void MapWidget::paintRoute() } SGVec2d p = project(g); - glColor4f(1.0, 0.0, 1.0, 1.0); + glColor3f(1.0, 0.0, 1.0); circleAtAlt(p, 8, 12, 5); std::ostringstream legend; @@ -912,7 +912,7 @@ void MapWidget::drawFlightHistory() // first pass, draw the actual lines glLineWidth(2.0); - glColor4f(0.0, 0.0, 1.0, 0.7); + glColor3f(0.0, 0.0, 1.0); glBegin(GL_LINE_STRIP); for (unsigned int i=0; i<_flightHistoryPath.size(); ++i) { @@ -987,7 +987,7 @@ void MapWidget::drawLatLonGrid() int ix = 0; int iy = 0; - glColor4f(0.8, 0.8, 0.8, 1.0); + glColor3f(0.8, 0.8, 0.8); glBegin(GL_LINES); bool didDraw; do { @@ -1038,7 +1038,7 @@ void MapWidget::drawGPSData() SGGeod trackRadial; SGGeodesy::direct(_aircraft, gpsTrackDeg, _drawRangeNm * SG_NM_TO_METER, trackRadial, az2); - glColor4f(1.0, 1.0, 0.0, 1.0); + glColor3f(1.0, 1.0, 0.0); glEnable(GL_LINE_STIPPLE); glLineStipple(1, 0x00FF); drawLine(project(_aircraft), project(trackRadial)); @@ -1049,7 +1049,7 @@ void MapWidget::drawGPSData() SGVec2d wp0Pos = project(wp0Geod); SGVec2d wp1Pos = project(wp1Geod); - glColor4f(1.0, 0.0, 1.0, 1.0); + glColor3f(1.0, 0.0, 1.0); drawLine(wp0Pos, wp1Pos); } @@ -1387,7 +1387,7 @@ void MapWidget::drawRunwayPre(FGRunway* rwy) glLineWidth(4.0); glColor3f(1.0, 0.0, 1.0); - drawLine(p1, p2); + drawLine(p1, p2); } void MapWidget::drawRunway(FGRunway* rwy) diff --git a/src/GUI/PathsDialog.cxx b/src/GUI/PathsDialog.cxx index d19879ebc..940a5ea8a 100644 --- a/src/GUI/PathsDialog.cxx +++ b/src/GUI/PathsDialog.cxx @@ -27,7 +27,8 @@ AddOnsPage::AddOnsPage(QWidget *parent, simgear::pkg::RootRef root) : m_catalogsModel = new CatalogListModel(this, m_packageRoot); m_ui->catalogsList->setModel(m_catalogsModel); - + connect(m_ui->catalogsList, &QListView::doubleClicked, + this, &AddOnsPage::onCatalogDoubleClicked); // enable drag-drop to re-order the paths m_ui->sceneryPathsList->setDragEnabled(true); m_ui->sceneryPathsList->setDragDropMode(QAbstractItemView::InternalMove); @@ -245,7 +246,12 @@ void AddOnsPage::addDefaultCatalog(QWidget* pr, bool silent) } dlg->setUrlAndDownload(url); dlg->exec(); +} +void AddOnsPage::onCatalogsRefreshed() +{ + m_catalogsModel->refresh(); + updateUi(); } void AddOnsPage::onRemoveCatalog() @@ -280,6 +286,18 @@ void AddOnsPage::onRemoveCatalog() updateUi(); } +void AddOnsPage::onCatalogDoubleClicked(const QModelIndex& index) +{ + if ((index.flags() & Qt::ItemIsEnabled) == false) { + // re-validate existing catalog + QScopedPointer dlg(new AddCatalogDialog(this, m_packageRoot)); + dlg->setUpdatingExistingCatalog(); + dlg->setUrlAndDownload(index.data(CatalogUrlRole).toUrl()); + dlg->exec(); + m_catalogsModel->refresh(); + } +} + void AddOnsPage::onInstallScenery() { QSettings settings; diff --git a/src/GUI/PathsDialog.hxx b/src/GUI/PathsDialog.hxx index 1361d4ac8..896bf7c7d 100644 --- a/src/GUI/PathsDialog.hxx +++ b/src/GUI/PathsDialog.hxx @@ -22,6 +22,9 @@ public: static void addDefaultCatalog(QWidget* pr, bool silent); +public slots: + void onCatalogsRefreshed(); + signals: void sceneryPathsChanged(); void aircraftPathsChanged(); @@ -43,6 +46,8 @@ private slots: void saveAircraftPaths(); void saveSceneryPaths(); + + void onCatalogDoubleClicked(const QModelIndex& index); private: void updateUi(); diff --git a/src/GUI/QmlAircraftInfo.cxx b/src/GUI/QmlAircraftInfo.cxx index dd86c4305..c449204e0 100644 --- a/src/GUI/QmlAircraftInfo.cxx +++ b/src/GUI/QmlAircraftInfo.cxx @@ -318,7 +318,8 @@ QVariantList QmlAircraftInfo::previews() const { if (_item) { QVariantList result; - Q_FOREACH(QUrl u, _item->previews) { + auto actualItem = resolveItem(); + Q_FOREACH(QUrl u, actualItem->previews) { result.append(u); } return result; @@ -488,7 +489,9 @@ void QmlAircraftInfo::setUri(QUrl u) auto ident = u.path().toStdString(); try { _package = globals->packageRoot()->getPackageById(ident); - _variant = _package->indexOfVariant(ident); + if (_package) { + _variant = _package->indexOfVariant(ident); + } } catch (sg_exception&) { qWarning() << "couldn't find package/variant for " << u; } diff --git a/src/GUI/new_gui.cxx b/src/GUI/new_gui.cxx index a2d6b3979..58cd8dfa2 100644 --- a/src/GUI/new_gui.cxx +++ b/src/GUI/new_gui.cxx @@ -21,6 +21,7 @@ #include #include +#include #include
#if defined(SG_UNIX) && !defined(SG_MAC) @@ -82,7 +83,17 @@ NewGUI::init () if (aircraftDialogDir.exists()) { readDir(aircraftDialogDir); } - + + // Read XML dialogs made available by registered add-ons + const auto& addonManager = flightgear::addons::AddonManager::instance(); + for (const auto& addon: addonManager->registeredAddons()) { + SGPath addonDialogDir = addon->getBasePath() / "gui/dialogs"; + + if (addonDialogDir.exists()) { + readDir(addonDialogDir); + } + } + // Fix for http://code.google.com/p/flightgear-bugs/issues/detail?id=947 fgGetNode("sim/menubar")->setAttribute(SGPropertyNode::PRESERVE, true); _menubar->init(); diff --git a/src/GUI/qml/AircraftCompactDelegate.qml b/src/GUI/qml/AircraftCompactDelegate.qml index 503ceded7..afa61fe70 100644 --- a/src/GUI/qml/AircraftCompactDelegate.qml +++ b/src/GUI/qml/AircraftCompactDelegate.qml @@ -95,6 +95,7 @@ Item { width: parent.width text: root.__showAlternateText ? root.alternateText() : model.description + font.pixelSize: Style.baseFontPixelSize maximumLineCount: 3 wrapMode: Text.WordWrap elide: Text.ElideRight diff --git a/src/GUI/qml/AircraftDetailsView.qml b/src/GUI/qml/AircraftDetailsView.qml index 2241ca9b8..3f85a1a24 100644 --- a/src/GUI/qml/AircraftDetailsView.qml +++ b/src/GUI/qml/AircraftDetailsView.qml @@ -100,7 +100,7 @@ Rectangle { width: parent.width wrapMode: Text.WordWrap visible: aircraft.description != "" - font.pixelSize: 14 + font.pixelSize: Style.baseFontPixelSize } Text { @@ -110,7 +110,7 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter wrapMode: Text.WordWrap visible: (aircraft.authors != undefined) - + font.pixelSize: Style.baseFontPixelSize } } @@ -146,6 +146,7 @@ Rectangle { Text { id: ratingsLabel text: qsTr("Ratings:") + font.pixelSize: Style.baseFontPixelSize } @@ -192,6 +193,7 @@ Rectangle { width: parent.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere visible: aircraft.pathOnDisk != undefined + font.pixelSize: Style.baseFontPixelSize } } // main layout column diff --git a/src/GUI/qml/AircraftDownloadPanel.qml b/src/GUI/qml/AircraftDownloadPanel.qml index 54dc49300..e488b9c5a 100644 --- a/src/GUI/qml/AircraftDownloadPanel.qml +++ b/src/GUI/qml/AircraftDownloadPanel.qml @@ -1,5 +1,6 @@ import QtQuick 2.0 import FlightGear.Launcher 1.0 +import "." // -> forces the qmldir to be loaded Item { id: root @@ -143,6 +144,7 @@ Item { anchors.leftMargin: 6 anchors.verticalCenter: button.verticalCenter text: "Size: " + (root.packageSize / 0x100000).toFixed(1) + "MB" + font.pixelSize: Style.baseFontPixelSize } Column { @@ -185,6 +187,7 @@ Item { visible: false text: (compact ? "" : "Downloaded ") + (root.downloadedBytes / 0x100000).toFixed(1) + "MB of " + (root.packageSize / 0x100000).toFixed(1) + "MB"; + font.pixelSize: Style.baseFontPixelSize } } // item container for progress bar and text } diff --git a/src/GUI/qml/AircraftRating.qml b/src/GUI/qml/AircraftRating.qml index e04487d68..08ec59d68 100644 --- a/src/GUI/qml/AircraftRating.qml +++ b/src/GUI/qml/AircraftRating.qml @@ -18,6 +18,7 @@ Item { horizontalAlignment: Text.AlignRight text: root.title + ":" + font.pixelSize: Style.baseFontPixelSize } Row { diff --git a/src/GUI/qml/AircraftVariantChoice.qml b/src/GUI/qml/AircraftVariantChoice.qml index 5ad5f4e08..9a11453cc 100644 --- a/src/GUI/qml/AircraftVariantChoice.qml +++ b/src/GUI/qml/AircraftVariantChoice.qml @@ -1,5 +1,6 @@ import QtQuick 2.0 import QtQuick.Window 2.0 +import "." // -> forces the qmldir to be loaded import FlightGear.Launcher 1.0 @@ -37,8 +38,9 @@ Rectangle { anchors.rightMargin: 4 horizontalAlignment: Text.AlignHCenter - font.pixelSize: 24 + font.pixelSize: Style.baseFontPixelSize * 2 text: aircraftInfo.name + fontSizeMode: Text.Fit elide: Text.ElideRight maximumLineCount: 1 diff --git a/src/GUI/qml/Button.qml b/src/GUI/qml/Button.qml index 4733c7434..97679b75a 100644 --- a/src/GUI/qml/Button.qml +++ b/src/GUI/qml/Button.qml @@ -20,6 +20,7 @@ Rectangle { anchors.centerIn: parent color: "white" text: (mouse.containsMouse && hoverText != "") ? root.hoverText : root.text + font.pixelSize: Style.baseFontPixelSize } MouseArea { diff --git a/src/GUI/qml/Slider.qml b/src/GUI/qml/Slider.qml index 9bfd3f4b1..901f37f74 100644 --- a/src/GUI/qml/Slider.qml +++ b/src/GUI/qml/Slider.qml @@ -20,6 +20,7 @@ Item { id: labelText width: parent.width - (emptyTrack.width + Style.margin) horizontalAlignment: Text.AlignRight + font.pixelSize: Style.baseFontPixelSize } Rectangle { diff --git a/src/GUI/qml/Style.qml b/src/GUI/qml/Style.qml index 26f117031..05f075d2d 100644 --- a/src/GUI/qml/Style.qml +++ b/src/GUI/qml/Style.qml @@ -18,5 +18,7 @@ QtObject readonly property string inactiveThemeColor: "#9f9f9f" readonly property string baseTextColor: "#3f3f3f" + + readonly property int baseFontPixelSize: 12 } diff --git a/src/GUI/qml/ToggleSwitch.qml b/src/GUI/qml/ToggleSwitch.qml index e47ff317a..2eb45c43b 100644 --- a/src/GUI/qml/ToggleSwitch.qml +++ b/src/GUI/qml/ToggleSwitch.qml @@ -45,6 +45,7 @@ Item { anchors.left: track.right anchors.leftMargin: Style.margin anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Style.baseFontPixelSize } MouseArea { 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++; diff --git a/src/Network/HTTPClient.cxx b/src/Network/HTTPClient.cxx index af0f19a76..d152a95cb 100644 --- a/src/Network/HTTPClient.cxx +++ b/src/Network/HTTPClient.cxx @@ -238,8 +238,9 @@ void FGHTTPClient::postinit() .method("refresh", &pkg::Catalog::refresh) .method("needingUpdate", &pkg::Catalog::packagesNeedingUpdate) .member("installed", &pkg::Catalog::installedPackages) - .method("search", &f_catalog_search); - + .method("search", &f_catalog_search) + .member("enabled", &pkg::Catalog::isEnabled); + NasalPackage::init("Package") .member("id", &pkg::Package::id) .member("name", &pkg::Package::name)