1
0
Fork 0

Merge /u/dancliff/flightgear/ branch launcher_addons_merge into next

https://sourceforge.net/p/flightgear/flightgear/merge-requests/151/
This commit is contained in:
James Turner 2019-03-04 15:20:44 +00:00
commit d5ce11ce55
11 changed files with 827 additions and 12 deletions

View file

@ -34,6 +34,7 @@
#include "addon_fwd.hxx"
#include "contacts.hxx"
#include "AddonVersion.hxx"
#include "pointer_traits.hxx"
namespace flightgear
{
@ -88,6 +89,13 @@ public:
// getMetadataFile()) and return the corresponding Addon instance.
static Addon fromAddonDir(const SGPath& addonPath);
template<class T>
static T fromAddonDir(const SGPath& addonPath)
{
using ptr_traits = shared_ptr_traits<T>;
return ptr_traits::makeStrongRef(fromAddonDir(addonPath));
}
std::string getId() const;
std::string getName() const;

View file

@ -14,9 +14,12 @@
#include <Main/globals.hxx>
#include <Network/HTTPClient.hxx>
#include "Add-ons/Addon.hxx"
#include "Add-ons/addon_fwd.hxx"
#include "LocalAircraftCache.hxx"
#include "LauncherMainWindow.hxx"
#include "CatalogListModel.hxx"
#include "AddonsModel.hxx"
#include "InstallSceneryDialog.hxx"
#include "QtLauncher.hxx"
@ -31,13 +34,38 @@ AddOnsController::AddOnsController(LauncherMainWindow *parent) :
connect(m_catalogs, &CatalogListModel::catalogsChanged, this, &AddOnsController::onCatalogsChanged);
m_addonsModuleModel = new AddonsModel(this);
connect(m_addonsModuleModel, &AddonsModel::modulesChanged, this, &AddOnsController::onAddonsChanged);
using namespace flightgear::addons;
QSettings settings;
m_sceneryPaths = settings.value("scenery-paths").toStringList();
m_aircraftPaths = settings.value("aircraft-paths").toStringList();
m_addonModulePaths = settings.value("addon-module-paths").toStringList();
int size = settings.beginReadArray("addon-modules");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
QString path = settings.value("path").toString();
m_addonModulePaths.push_back( path );
const SGPath addonPath(path.toStdString());
bool enable = settings.value("enable").toBool();
try {
auto addon = Addon::fromAddonDir<AddonRef>(addonPath);
m_addonsModuleModel->append(path, addon, enable);
}
catch (const sg_exception &e) {
string msg = "Error getting add-on metadata: " + e.getFormattedMessage();
SG_LOG(SG_GENERAL, SG_ALERT, msg);
}
}
settings.endArray();
qmlRegisterUncreatableType<AddOnsController>("FlightGear.Launcher", 1, 0, "AddOnsControllers", "no");
qmlRegisterUncreatableType<CatalogListModel>("FlightGear.Launcher", 1, 0, "CatalogListModel", "no");
qmlRegisterUncreatableType<AddonsModel>("FlightGear.Launcher", 1, 0, "AddonsModel", "no");
}
QStringList AddOnsController::aircraftPaths() const
@ -107,7 +135,7 @@ QString AddOnsController::addAddOnModulePath() const
SGPath p(path.toStdString());
bool isValid = false;
for (const auto& file: {"addon-config.xml", "addon-main.nas"}) {
for (const auto& file: {"addon-metadata.xml", "addon-main.nas"}) {
if ((p / file).exists()) {
isValid = true;
break;
@ -120,7 +148,7 @@ QString AddOnsController::addAddOnModulePath() const
mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
mb.setDefaultButton(QMessageBox::No);
mb.setInformativeText(tr("Added modules should contain at least both of the following "
"files: addon-config.xml, addon-main.nas."));
"files: addon-metadata.xml, addon-main.nas."));
mb.exec();
if (mb.result() == QMessageBox::No) {
@ -128,6 +156,20 @@ QString AddOnsController::addAddOnModulePath() const
}
}
using namespace flightgear::addons;
const SGPath addonPath(path.toStdString());
try {
// when newly added, enable by default
auto addon = Addon::fromAddonDir<AddonRef>(addonPath);
if (!m_addonsModuleModel->append(path, addon, true)) {
path = QString("");
}
} catch (const sg_exception &e) {
string msg = "Error getting add-on metadata: " + e.getFormattedMessage();
SG_LOG(SG_GENERAL, SG_ALERT, msg);
}
return path;
}
@ -216,6 +258,12 @@ void AddOnsController::setSceneryPaths(QStringList sceneryPaths)
emit sceneryPathsChanged(m_sceneryPaths);
}
void AddOnsController::setAddons(AddonsModel* addons)
{
}
void AddOnsController::setModulePaths(QStringList modulePaths)
{
if (m_addonModulePaths == modulePaths)
@ -223,10 +271,22 @@ void AddOnsController::setModulePaths(QStringList modulePaths)
m_addonModulePaths = modulePaths;
m_addonsModuleModel->resetData(modulePaths);
QSettings settings;
settings.setValue("addon-module-paths", m_addonModulePaths);
int i = 0;
settings.beginWriteArray("addon-modules");
for (const QString& path : modulePaths) {
if (m_addonsModuleModel->containsPath(path)) {
settings.setArrayIndex(i++);
settings.setValue("path", path);
settings.setValue("enable", m_addonsModuleModel->getPathEnable(path));
}
}
settings.endArray();
emit modulePathsChanged(m_addonModulePaths);
emit modulesChanged();
}
void AddOnsController::officialCatalogAction(QString s)
@ -268,3 +328,17 @@ void AddOnsController::onCatalogsChanged()
emit isOfficialHangarRegisteredChanged();
}
void AddOnsController::onAddonsChanged()
{
QSettings settings;
int i = 0;
settings.beginWriteArray("addon-modules");
for (const auto& path : m_addonModulePaths) {
settings.setArrayIndex(i++);
settings.setValue("path", path);
settings.setValue("enable", m_addonsModuleModel->getPathEnable(path));
}
settings.endArray();
}

View file

@ -5,6 +5,7 @@
#include <QStringList>
class CatalogListModel;
class AddonsModel;
class LauncherMainWindow;
class AddOnsController : public QObject
@ -16,7 +17,7 @@ class AddOnsController : public QObject
Q_PROPERTY(QStringList modulePaths READ modulePaths WRITE setModulePaths NOTIFY modulePathsChanged)
Q_PROPERTY(CatalogListModel* catalogs READ catalogs CONSTANT)
Q_PROPERTY(AddonsModel* modules READ modules NOTIFY modulesChanged)
Q_PROPERTY(bool isOfficialHangarRegistered READ isOfficialHangarRegistered NOTIFY isOfficialHangarRegisteredChanged)
Q_PROPERTY(bool showNoOfficialHangar READ showNoOfficialHangar NOTIFY showNoOfficialHangarChanged)
@ -30,6 +31,9 @@ public:
CatalogListModel* catalogs() const
{ return m_catalogs; }
AddonsModel* modules() const
{ return m_addonsModuleModel; }
Q_INVOKABLE QString addAircraftPath() const;
Q_INVOKABLE QString addSceneryPath() const;
Q_INVOKABLE QString addAddOnModulePath() const;
@ -44,10 +48,12 @@ public:
bool showNoOfficialHangar() const;
Q_INVOKABLE void officialCatalogAction(QString s);
signals:
void aircraftPathsChanged(QStringList aircraftPaths);
void sceneryPathsChanged(QStringList sceneryPaths);
void modulePathsChanged(QStringList modulePaths);
void modulesChanged();
void isOfficialHangarRegisteredChanged();
void showNoOfficialHangarChanged();
@ -56,6 +62,8 @@ public slots:
void setAircraftPaths(QStringList aircraftPaths);
void setSceneryPaths(QStringList sceneryPaths);
void setModulePaths(QStringList modulePaths);
void setAddons(AddonsModel* addons);
void onAddonsChanged(void);
private:
bool shouldShowOfficialCatalogMessage() const;
@ -63,6 +71,8 @@ private:
LauncherMainWindow* m_launcher;
CatalogListModel* m_catalogs = nullptr;
AddonsModel* m_addonsModuleModel = nullptr;
QStringList m_aircraftPaths;
QStringList m_sceneryPaths;
QStringList m_addonModulePaths;

223
src/GUI/AddonsModel.cxx Normal file
View file

@ -0,0 +1,223 @@
// AddonsModel.cxx - part of GUI launcher using Qt5
//
// Written by Dan Wickstrom, started February 2019.
//
// Copyright (C) 2019 Daniel Wickstrom <daniel.c.wickstrom@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <QDebug>
#include <QUrl>
#include <string>
#include <simgear/misc/strutils.hxx>
#include <Include/version.h>
#include "AddonsModel.hxx"
#include "Add-ons/AddonMetadataParser.hxx"
AddonsModel::AddonsModel(QObject* pr) :
QAbstractListModel(pr)
{
m_roleToName[Qt::DisplayRole] = "display";
m_nameToRole["display"] = Qt::DisplayRole;
int roleValue = IdRole;
for (auto it = m_roles.begin(); it != m_roles.end(); ++it) {
QByteArray name = (*it).toLocal8Bit();
m_roleToName[roleValue] = name;
m_nameToRole[*it] = roleValue++;
}
}
AddonsModel::~AddonsModel()
{
}
void AddonsModel::resetData(const QStringList& ndata)
{
beginResetModel();
QSet<QString> newSet = QSet<QString>::fromList(ndata);
for(const auto& path : m_addonsList) {
if (!newSet.contains(path)) {
m_addonsMap.remove(path);
}
}
m_addonsList = ndata;
endResetModel();
}
int AddonsModel::rowCount(const QModelIndex& parent) const
{
return m_addonsList.size();
}
QVariant AddonsModel::data(const QModelIndex& index, int role) const
{
auto idx = index.row();
return get(idx, role);
}
QVariant AddonsModel::get(int idx, QString role) const
{
int role_idx = m_nameToRole[role];
return get(idx, role_idx);
}
QVariant AddonsModel::get(int idx, int role) const
{
if (idx >= 0 && idx < m_addonsList.size()) {
auto path = m_addonsList[idx];
auto addon = m_addonsMap[path].addon;
if (role == Qt::DisplayRole) {
QString name = QString::fromStdString(addon->getName());
QString desc = QString::fromStdString(addon->getShortDescription());
return tr("%1 - %2").arg(name).arg(desc);
}
else if (role == IdRole) {
return QString::fromStdString(addon->getId());
}
else if (role == NameRole) {
return QString::fromStdString(addon->getName());
}
else if (role == PathRole) {
return path;
}
else if (role == VersionRole) {
return QString::fromStdString(addon->getVersion()->str());
}
else if (role == AuthorsRole) {
QStringList authors;
for (auto author : addon->getAuthors()) {
authors.push_back(QString::fromStdString(author->getName()));
}
return authors;
}
else if (role == MaintainersRole) {
QStringList maintainers;
for (auto maintainer : addon->getMaintainers()) {
maintainers.push_back(QString::fromStdString(maintainer->getName()));
}
return maintainers;
}
else if (role == ShortDescriptionRole) {
return QString::fromStdString(addon->getShortDescription());
}
else if (role == LongDescriptionRole) {
return QString::fromStdString(addon->getLongDescription());
}
else if (role == LicenseDesignationRole) {
return QString::fromStdString(addon->getLicenseDesignation());
}
else if (role == LicenseUrlRole) {
return QUrl(QString::fromStdString(addon->getLicenseUrl()));
}
else if (role == TagsRole) {
QStringList tags;
for (auto tag : addon->getTags()) {
tags.push_back(QString::fromStdString(tag));
}
return tags;
}
else if (role == MinFGVersionRole) {
return QString::fromStdString(addon->getMinFGVersionRequired());
}
else if (role == MaxFGVersionRole) {
return QString::fromStdString(addon->getMaxFGVersionRequired());
}
else if (role == HomePageRole) {
return QUrl(QString::fromStdString(addon->getHomePage()));
}
else if (role == DownloadUrlRole) {
return QUrl(QString::fromStdString(addon->getDownloadUrl()));
}
else if (role == SupportUrlRole) {
return QUrl(QString::fromStdString(addon->getSupportUrl()));
}
else if (role == CodeRepoUrlRole) {
return QUrl(QString::fromStdString(addon->getCodeRepositoryUrl()));
}
else if (role == EnableRole) {
return QVariant(m_addonsMap[path].enable && checkVersion(path));
}
}
return QVariant();
}
bool AddonsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
return false;
}
bool AddonsModel::append(QString path, flightgear::addons::AddonRef& addon, bool enable) {
if (!m_addonsMap.contains(path)) {
m_addonsList.push_back(path);
m_addonsMap[path].addon = addon;
m_addonsMap[path].enable = enable && checkVersion(path);
emit dataChanged(index(m_addonsList.size()-1), index(m_addonsList.size()-1));
return true;
}
return false;
}
Qt::ItemFlags AddonsModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
QHash<int, QByteArray> AddonsModel::roleNames() const
{
return m_roleToName;
}
void AddonsModel::enable(int index, bool enable)
{
if ((index < 0) || (index >= m_addonsList.size())) {
return;
}
auto path = m_addonsList[index];
m_addonsMap[path].enable = enable && checkVersion(path);
emit modulesChanged();
}
bool AddonsModel::checkVersion(QString path) const
{
using namespace simgear;
// Check that the FlightGear version satisfies the add-on requirements
std::string minFGversion = m_addonsMap[path].addon->getMinFGVersionRequired();
if (strutils::compare_versions(FLIGHTGEAR_VERSION, minFGversion) < 0) {
return false;
}
std::string maxFGversion = m_addonsMap[path].addon->getMaxFGVersionRequired();
if (maxFGversion != "none" &&
strutils::compare_versions(FLIGHTGEAR_VERSION, maxFGversion) > 0) {
return false;
}
return true;
}

130
src/GUI/AddonsModel.hxx Normal file
View file

@ -0,0 +1,130 @@
// AddonsModel.hxx - part of GUI launcher using Qt5
//
// Written by Dan Wickstrom, started February 2019.
//
// Copyright (C) 2019 Daniel Wickstrom <daniel.c.wickstrom@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_GUI_METADATA_LIST_MODEL
#define FG_GUI_METADATA_LIST_MODEL
#include <QAbstractListModel>
#include <QDateTime>
#include <QDir>
#include <QPixmap>
#include <QStringList>
#include <set>
#include <simgear/package/Root.hxx>
#include <simgear/package/Catalog.hxx>
#include "Add-ons/addon_fwd.hxx"
#include "Add-ons/Addon.hxx"
#include "Add-ons/AddonMetadataParser.hxx"
class AddonsModel : public QAbstractListModel
{
Q_OBJECT
using AddonsMeta = struct { bool enable; flightgear::addons::AddonRef addon; };
using AddonsMap = QHash<QString, AddonsMeta>;
public:
AddonsModel(QObject* pr);
~AddonsModel();
int rowCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
Q_INVOKABLE QVariant get(int index, int role) const;
Q_INVOKABLE QVariant get(int index, QString role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QHash<int, QByteArray> roleNames() const override;
bool append(QString path, flightgear::addons::AddonRef& addon, bool enable);
void resetData(const QStringList& ndata);
Q_INVOKABLE bool checkVersion(QString path) const;
bool getPathEnable(const QString& path) { return m_addonsMap[path].enable; }
bool containsPath(const QString& path) { return m_addonsMap.contains(path); }
Q_INVOKABLE void enable(int index, bool enable);
// this must stay in sync with m_roles stringlist
enum AddonRoles
{
IdRole = Qt::UserRole + 1,
NameRole,
PathRole,
VersionRole,
AuthorsRole,
MaintainersRole,
ShortDescriptionRole,
LongDescriptionRole,
LicenseDesignationRole,
LicenseUrlRole,
TagsRole,
MinFGVersionRole,
MaxFGVersionRole,
HomePageRole,
DownloadUrlRole,
SupportUrlRole,
CodeRepoUrlRole,
EnableRole
};
Q_ENUMS(AddonRoles)
signals:
void modulesChanged();
private:
// this must stay in sync with MetaRoles enum
const QStringList m_roles = {
"id",
"name",
"path",
"version",
"authors",
"maintainers",
"short_description",
"long_description",
"license_designation",
"license_url",
"tags",
"minversion_fg",
"maxversion_fg",
"homepage",
"download_url",
"support_url",
"coderepo_url",
"enable"
};
QStringList m_addonsList;
AddonsMap m_addonsMap;
QHash<int, QByteArray> m_roleToName;
QHash<QString, int> m_nameToRole;
};
#endif // of Metadata_LIST_MODEL

View file

@ -120,6 +120,8 @@ if (HAVE_QT)
LauncherController.hxx
AddOnsController.cxx
AddOnsController.hxx
AddonsModel.cxx
AddonsModel.hxx
PixmapImageItem.cxx
PixmapImageItem.hxx
${uic_sources}

View file

@ -292,9 +292,19 @@ void LauncherController::collectAircraftArgs()
m_config->setArg("fg-aircraft", path);
}
Q_FOREACH(QString path, settings.value("addon-module-paths").toStringList()) {
m_config->setArg("addon", path);
// add-on module paths
int size = settings.beginReadArray("addon-modules");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
QString path = settings.value("path").toString();
bool enable = settings.value("enable").toBool();
if (enable) {
m_config->setArg("addon", path);
}
}
settings.endArray();
}
void LauncherController::saveAircraft()

View file

@ -3,12 +3,38 @@ import FlightGear.Launcher 1.0
import "."
Item {
id: root
Flickable {
id: flick
height: parent.height
width: parent.width - scrollbar.width
flickableDirection: Flickable.VerticalFlick
contentHeight: contents.childrenRect.height
function showDetails(index)
{
// set URI, start animation
// change state
detailsView.mdx = index;
detailsView.visible = true
contents.visible = false
}
function goBack()
{
detailsView.visible = false
contents.visible = true
}
AddonsDetailsView
{
id: detailsView
anchors.fill: parent
visible: false
onGoBack: {
flick.goBack();
}
}
Column {
id: contents
@ -144,7 +170,7 @@ Item {
"know the folder(s) containing the Add-on Modules.")
showAddButton: true
onAdd: {
var newPath =_addOns.addAddOnModulePath();
var newPath = _addOns.addAddOnModulePath();
if (newPath !== "") {
_addOns.modulePaths.push(newPath)
}
@ -165,10 +191,10 @@ Item {
Repeater {
id: addonModulesPathsRepeater
model: _addOns.modulePaths
delegate: PathListDelegate {
model: _addOns.modules
delegate: AddOnsDelegate {
width: addonModulePathsColumn.width
deletePromptText: qsTr("Remove the add-on module folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData);
deletePromptText: qsTr("Remove the add-on module folder: '%1' from the list? (The folder contents will not be changed)").arg(model.path);
modelCount: _addOns.modulePaths.length
onPerformDelete: {
@ -180,9 +206,13 @@ Item {
onPerformMove: {
var modifiedPaths = _addOns.modulePaths.slice()
modifiedPaths.splice(model.index, 1);
modifiedPaths.splice(newIndex, 0, modelData)
modifiedPaths.splice(newIndex, 0, model.path)
_addOns.modulePaths = modifiedPaths;
}
onShowDetails: {
flick.showDetails(detailIndex)
}
}
}

View file

@ -0,0 +1,139 @@
import QtQuick 2.4
import FlightGear.Launcher 1.0
import "."
Item {
id: delegateRoot
signal performDelete();
signal performMove(var newIndex);
signal showDetails(var detailIndex);
property alias deletePromptText: confirmDeletePath.promptText
property int modelCount: 0
height: childrenRect.height
function displayLabel(lbl) {
return qsTr("%1 %2").arg(lbl).arg((_addOns.modules.checkVersion(model.path) ? "" : " (disabled due to incompatible FG version)"));
}
Item {
id: divider
visible: model.index > 0 // divider before each item except the first
height: Style.margin
width: parent.width
Rectangle {
color: Style.frameColor
height: 1
width: parent.width - Style.strutSize
anchors.centerIn: parent
}
}
// this is the main visible content
Item {
id: contentRect
anchors.top: divider.bottom
height: Math.max(label.implicitHeight, pathDeleteButton.height)
width: delegateRoot.width
Checkbox {
id: chkbox
checked: model.enable
width: 30
anchors.left: parent.left
anchors.right: addonsDelegateHover.left
anchors.rightMargin: Style.margin
height: contentRect.height
onCheckedChanged: {
_addOns.modules.enable(model.index, checked)
}
}
MouseArea {
id: addonsDelegateHover
anchors.leftMargin: Style.margin
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: chkbox.right
hoverEnabled: true
acceptedButtons: Qt.NoButton
// it's important that the button and text are children of the
// MouseArea, so nested containsMouse logic works
ClickableText {
id: label
text: delegateRoot.displayLabel(model.display)
onClicked: {
// open the location
delegateRoot.showDetails(model.index)
}
anchors.left: addonsDelegateHover.left
anchors.right: reorderButton.left
anchors.rightMargin: Style.margin
height: contentRect.height
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
}
DeleteButton {
id: pathDeleteButton
anchors.right: parent.right
visible: addonsDelegateHover.containsMouse
onClicked: {
confirmDeletePath.visible = true
}
}
DragToReorderButton {
id: reorderButton
anchors.right: pathDeleteButton.left
anchors.rightMargin: Style.margin
visible: addonsDelegateHover.containsMouse && (canMoveDown || canMoveUp)
canMoveUp: model.index > 0
canMoveDown: model.index < (delegateRoot.modelCount - 1)
onMoveUp: {
if (model.index === 0)
return;
delegateRoot.performMove(model.index - 1)
}
onMoveDown: {
delegateRoot.performMove(model.index + 1)
}
}
}
YesNoPanel {
id: confirmDeletePath
visible: false
anchors.fill: parent
yesText: qsTr("Remove")
noText: qsTr("Cancel")
yesIsDestructive: true
onAccepted: {
delegateRoot.performDelete()
}
onRejected: {
confirmDeletePath.visible = false
}
Rectangle {
anchors.fill: parent
z: -1
}
}
}
}

View file

@ -0,0 +1,187 @@
import QtQuick 2.4
import FlightGear.Launcher 1.0
import "."
Rectangle {
id: root
color: "white"
property int mdx: 0
signal goBack()
MouseArea {
// consume all mouse-clicks on the detail view
anchors.fill: parent
}
Flickable
{
id: flickable
anchors.fill: parent
contentWidth: parent.width
contentHeight: content.childrenRect.height
boundsBehavior: Flickable.StopAtBounds
function labelText(lbl, idx, key) {
var value = _addOns.modules.get(idx, key);
if (typeof value === 'object') {
value = Object.keys(value).map(function(key) { return value[key]; }).join(', ');
}
return qsTr("%1: %2").arg(lbl).arg(value);
}
function getMeta(idx, key) {
return _addOns.modules.get(idx, key);
}
Item {
id: content
width: root.width - scrollbar.width
height: childrenRect.height
Column {
id: columnTop
width: content.width - (Style.margin * 2)
spacing: Style.margin
anchors.horizontalCenter: parent.horizontalCenter
// description + authors container
Item {
width: parent.width
height: childrenRect.height
Column {
id: column
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
spacing: Style.margin
StyledText {
id: addonName
text: flickable.labelText("Module", mdx, "name");
width: parent.width
wrapMode: Text.WordWrap
visible: flickable.getMeta(mdx, "name") != ""
}
StyledText {
id: addonDescription
text: flickable.labelText("Description", mdx, "long_description");
width: parent.width
wrapMode: Text.WordWrap
visible: flickable.getMeta(mdx, "long_description") != ""
}
StyledText {
id: addonId
text: flickable.labelText("Id", mdx, "id");
width: parent.width
wrapMode: Text.WordWrap
visible: flickable.getMeta(mdx, "id") != ""
}
StyledText {
id: addonVersion
text: flickable.labelText("Version", mdx, "version");
width: parent.width
wrapMode: Text.WordWrap
visible: flickable.getMeta(mdx, "version") != ""
}
StyledText {
id: minFgVersion
text: flickable.labelText("Min FG Version", mdx, "minversion_fg");
width: parent.width
wrapMode: Text.WordWrap
visible: flickable.getMeta(mdx, "minversion_fg") != ""
}
StyledText {
id: maxFgVersion
text: flickable.labelText("Max FG Version", mdx, "maxversion_fg");
width: parent.width
wrapMode: Text.WordWrap
visible: flickable.getMeta(mdx, "maxversion_fg") != ""
}
StyledText {
id: addonAuthors
text: flickable.labelText("Authors", mdx, "authors");
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
visible: (flickable.getMeta(mdx, "authors") != undefined)
}
StyledText {
id: addonMaintainers
text: flickable.labelText("Maintainers", mdx, "maintainers");
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
visible: (flickable.getMeta(mdx, "maintainers") != undefined)
}
}
} // end of Item
// web-links row
Row {
id: weblinks
width: parent.width
height: childrenRect.height
spacing: Style.margin
Weblink {
visible: flickable.getMeta(mdx, "homepage") != ""
label: qsTr("Website")
link: flickable.getMeta(mdx, "homepage") || ""
}
Weblink {
visible: flickable.getMeta(mdx, "support_url") != ""
label: qsTr("Support and issue reporting")
link: flickable.getMeta(mdx, "support_url") || ""
}
Weblink {
visible: flickable.getMeta(mdx, "download_url") != ""
label: qsTr("Download")
link: flickable.getMeta(mdx, "download_url") || ""
}
}
ClickableText {
id: pathLabel
text: flickable.labelText("Local file location", mdx, "path");
onClicked: {
// open the location
_addOns.openDirectory(flickable.getMeta(mdx, "path"))
}
// anchors.top: weblinks.bottom
anchors.left: parent.left
width: parent.width
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
visible: flickable.getMeta(mdx, "path") != undefined
}
Text {
height: 50
text: " "
}
Button
{
width: Style.strutSize
id: backButton
text: "< Back"
onClicked: root.goBack();
}
} // main layout column
} // of main item
} // of Flickable
} // of Rect

View file

@ -85,6 +85,8 @@
<file>qml/DeleteButton.qml</file>
<file>qml/YesNoPanel.qml</file>
<file>qml/PathListDelegate.qml</file>
<file>qml/AddOnsDelegate.qml</file>
<file>qml/AddonsDetailsView.qml</file>
<file>qml/AddCatalogPanel.qml</file>
<file>qml/LineEdit.qml</file>
<file>qml/LocationAirportView.qml</file>