Stubbing out Add-ons controller
Also CatalogListModel stubs
This commit is contained in:
parent
3b79530b06
commit
205fadbff3
27 changed files with 1560 additions and 158 deletions
216
src/GUI/AddOnsController.cxx
Normal file
216
src/GUI/AddOnsController.cxx
Normal file
|
@ -0,0 +1,216 @@
|
|||
#include "AddOnsController.hxx"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
#include <QQmlComponent>
|
||||
#include <QDesktopServices>
|
||||
#include <QValidator>
|
||||
|
||||
#include <simgear/package/Root.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Network/HTTPClient.hxx>
|
||||
|
||||
#include "LocalAircraftCache.hxx"
|
||||
#include "LauncherMainWindow.hxx"
|
||||
#include "CatalogListModel.hxx"
|
||||
#include "InstallSceneryDialog.hxx"
|
||||
#include "QtLauncher.hxx"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AddOnsController::AddOnsController(LauncherMainWindow *parent) :
|
||||
QObject(parent),
|
||||
m_launcher(parent)
|
||||
{
|
||||
m_catalogs = new CatalogListModel(this,
|
||||
simgear::pkg::RootRef(globals->packageRoot()));
|
||||
|
||||
connect(m_catalogs, &CatalogListModel::catalogsChanged, this, &AddOnsController::onCatalogsChanged);
|
||||
|
||||
QSettings settings;
|
||||
m_sceneryPaths = settings.value("scenery-paths").toStringList();
|
||||
m_aircraftPaths = settings.value("aircraft-paths").toStringList();
|
||||
|
||||
qmlRegisterUncreatableType<AddOnsController>("FlightGear.Launcher", 1, 0, "AddOnsControllers", "no");
|
||||
qmlRegisterUncreatableType<CatalogListModel>("FlightGear.Launcher", 1, 0, "CatalogListModel", "no");
|
||||
}
|
||||
|
||||
QStringList AddOnsController::aircraftPaths() const
|
||||
{
|
||||
return m_aircraftPaths;
|
||||
}
|
||||
|
||||
QStringList AddOnsController::sceneryPaths() const
|
||||
{
|
||||
return m_sceneryPaths;
|
||||
}
|
||||
|
||||
QString AddOnsController::addAircraftPath() const
|
||||
{
|
||||
QString path = QFileDialog::getExistingDirectory(m_launcher, tr("Choose aircraft folder"));
|
||||
if (path.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// the user might add a directory containing an 'Aircraft' subdir. Let's attempt
|
||||
// to check for that case and handle it gracefully.
|
||||
bool pathOk = false;
|
||||
if (LocalAircraftCache::isCandidateAircraftPath(path)) {
|
||||
pathOk = true;
|
||||
} else {
|
||||
// no aircraft in speciied path, look for Aircraft/ subdir
|
||||
QDir d(path);
|
||||
if (d.exists("Aircraft")) {
|
||||
QString p2 = d.filePath("Aircraft");
|
||||
if (LocalAircraftCache::isCandidateAircraftPath(p2)) {
|
||||
pathOk = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathOk) {
|
||||
QMessageBox mb;
|
||||
mb.setText(tr("No aircraft found in the folder '%1' - add anyway?").arg(path));
|
||||
mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
mb.setDefaultButton(QMessageBox::No);
|
||||
mb.exec();
|
||||
|
||||
if (mb.result() == QMessageBox::No) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
QString AddOnsController::addSceneryPath() const
|
||||
{
|
||||
QString path = QFileDialog::getExistingDirectory(m_launcher, tr("Choose scenery folder"));
|
||||
if (path.isEmpty()) {
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
// validation
|
||||
SGPath p(path.toStdString());
|
||||
bool isValid = false;
|
||||
|
||||
for (const auto& dir: {"Objects", "Terrain", "Buildings", "Roads", "Pylons", "NavData"}) {
|
||||
if ((p / dir).exists()) {
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
QMessageBox mb;
|
||||
mb.setText(tr("The folder '%1' doesn't appear to contain scenery - add anyway?").arg(path));
|
||||
mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
mb.setDefaultButton(QMessageBox::No);
|
||||
mb.setInformativeText(tr("Added scenery should contain at least one of the following "
|
||||
"folders: Objects, Terrain, Buildings, Roads, Pylons, NavData."));
|
||||
mb.exec();
|
||||
|
||||
if (mb.result() == QMessageBox::No) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
QString AddOnsController::installCustomScenery()
|
||||
{
|
||||
QSettings settings;
|
||||
QString downloadDir = settings.value("download-dir").toString();
|
||||
InstallSceneryDialog dlg(m_launcher, downloadDir);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
return dlg.sceneryPath();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void AddOnsController::openDirectory(QString path)
|
||||
{
|
||||
QUrl u = QUrl::fromLocalFile(path);
|
||||
QDesktopServices::openUrl(u);
|
||||
}
|
||||
|
||||
void AddOnsController::setAircraftPaths(QStringList aircraftPaths)
|
||||
{
|
||||
if (m_aircraftPaths == aircraftPaths)
|
||||
return;
|
||||
|
||||
qInfo() << Q_FUNC_INFO << aircraftPaths << ", was " << m_aircraftPaths;
|
||||
|
||||
m_aircraftPaths = aircraftPaths;
|
||||
emit aircraftPathsChanged(m_aircraftPaths);
|
||||
|
||||
|
||||
QSettings settings;
|
||||
settings.setValue("aircraft-paths", m_aircraftPaths);
|
||||
auto aircraftCache = LocalAircraftCache::instance();
|
||||
aircraftCache->setPaths(m_aircraftPaths);
|
||||
aircraftCache->scanDirs();
|
||||
}
|
||||
|
||||
void AddOnsController::setSceneryPaths(QStringList sceneryPaths)
|
||||
{
|
||||
if (m_sceneryPaths == sceneryPaths)
|
||||
return;
|
||||
|
||||
m_sceneryPaths = sceneryPaths;
|
||||
|
||||
QSettings settings;
|
||||
settings.setValue("scenery-paths", m_sceneryPaths);
|
||||
|
||||
flightgear::launcherSetSceneryPaths();
|
||||
|
||||
emit sceneryPathsChanged(m_sceneryPaths);
|
||||
}
|
||||
|
||||
void AddOnsController::officialCatalogAction(QString s)
|
||||
{
|
||||
if (s == "hide") {
|
||||
QSettings settings;
|
||||
settings.setValue("hide-official-catalog-message", true);
|
||||
} else if (s == "add-official") {
|
||||
// TEMPROARY, FIXME
|
||||
// AddOnsPage::addDefaultCatalog(nullptr, false /* not silent */);
|
||||
}
|
||||
|
||||
emit showNoOfficialHangarChanged();
|
||||
}
|
||||
|
||||
bool AddOnsController::shouldShowOfficialCatalogMessage() const
|
||||
{
|
||||
QSettings settings;
|
||||
bool showOfficialCatalogMesssage = !globals->get_subsystem<FGHTTPClient>()->isDefaultCatalogInstalled();
|
||||
if (settings.value("hide-official-catalog-message").toBool()) {
|
||||
showOfficialCatalogMesssage = false;
|
||||
}
|
||||
return showOfficialCatalogMesssage;
|
||||
}
|
||||
|
||||
|
||||
bool AddOnsController::isOfficialHangarRegistered()
|
||||
{
|
||||
return globals->get_subsystem<FGHTTPClient>()->isDefaultCatalogInstalled();
|
||||
}
|
||||
|
||||
bool AddOnsController::showNoOfficialHangar() const
|
||||
{
|
||||
return shouldShowOfficialCatalogMessage();
|
||||
}
|
||||
|
||||
void AddOnsController::onCatalogsChanged()
|
||||
{
|
||||
emit showNoOfficialHangarChanged();
|
||||
emit isOfficialHangarRegisteredChanged();
|
||||
}
|
||||
|
64
src/GUI/AddOnsController.hxx
Normal file
64
src/GUI/AddOnsController.hxx
Normal file
|
@ -0,0 +1,64 @@
|
|||
#ifndef ADDONSCONTROLLER_HXX
|
||||
#define ADDONSCONTROLLER_HXX
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class CatalogListModel;
|
||||
class LauncherMainWindow;
|
||||
|
||||
class AddOnsController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QStringList aircraftPaths READ aircraftPaths WRITE setAircraftPaths NOTIFY aircraftPathsChanged)
|
||||
Q_PROPERTY(QStringList sceneryPaths READ sceneryPaths WRITE setSceneryPaths NOTIFY sceneryPathsChanged)
|
||||
|
||||
Q_PROPERTY(CatalogListModel* catalogs READ catalogs CONSTANT)
|
||||
|
||||
Q_PROPERTY(bool isOfficialHangarRegistered READ isOfficialHangarRegistered NOTIFY isOfficialHangarRegisteredChanged)
|
||||
Q_PROPERTY(bool showNoOfficialHangar READ showNoOfficialHangar NOTIFY showNoOfficialHangarChanged)
|
||||
|
||||
public:
|
||||
explicit AddOnsController(LauncherMainWindow *parent = nullptr);
|
||||
|
||||
QStringList aircraftPaths() const;
|
||||
QStringList sceneryPaths() const;
|
||||
|
||||
CatalogListModel* catalogs() const
|
||||
{ return m_catalogs; }
|
||||
|
||||
Q_INVOKABLE QString addAircraftPath() const;
|
||||
Q_INVOKABLE QString addSceneryPath() const;
|
||||
|
||||
// we would ideally do this in-page, but needs some extra work
|
||||
Q_INVOKABLE QString installCustomScenery();
|
||||
|
||||
Q_INVOKABLE void openDirectory(QString path);
|
||||
|
||||
bool isOfficialHangarRegistered();
|
||||
|
||||
bool showNoOfficialHangar() const;
|
||||
|
||||
Q_INVOKABLE void officialCatalogAction(QString s);
|
||||
signals:
|
||||
void aircraftPathsChanged(QStringList aircraftPaths);
|
||||
void sceneryPathsChanged(QStringList sceneryPaths);
|
||||
|
||||
void isOfficialHangarRegisteredChanged();
|
||||
void showNoOfficialHangarChanged();
|
||||
|
||||
public slots:
|
||||
void setAircraftPaths(QStringList aircraftPaths);
|
||||
void setSceneryPaths(QStringList sceneryPaths);
|
||||
|
||||
private:
|
||||
bool shouldShowOfficialCatalogMessage() const;
|
||||
void onCatalogsChanged();
|
||||
|
||||
LauncherMainWindow* m_launcher;
|
||||
CatalogListModel* m_catalogs = nullptr;
|
||||
QStringList m_aircraftPaths;
|
||||
QStringList m_sceneryPaths;
|
||||
};
|
||||
|
||||
#endif // ADDONSCONTROLLER_HXX
|
|
@ -92,10 +92,6 @@ if (HAVE_QT)
|
|||
AircraftModel.cxx
|
||||
CatalogListModel.cxx
|
||||
CatalogListModel.hxx
|
||||
AddCatalogDialog.cxx
|
||||
AddCatalogDialog.hxx
|
||||
PathsDialog.cxx
|
||||
PathsDialog.hxx
|
||||
LocationWidget.cxx
|
||||
LocationWidget.hxx
|
||||
QtMessageBox.cxx
|
||||
|
@ -126,6 +122,8 @@ if (HAVE_QT)
|
|||
RecentLocationsModel.hxx
|
||||
LauncherController.cxx
|
||||
LauncherController.hxx
|
||||
AddOnsController.cxx
|
||||
AddOnsController.hxx
|
||||
${uic_sources}
|
||||
${qrc_sources}
|
||||
${qml_sources})
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QTimer>
|
||||
|
||||
// Simgear
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
@ -32,25 +33,61 @@
|
|||
|
||||
// FlightGear
|
||||
#include <Main/globals.hxx>
|
||||
#include <Network/HTTPClient.hxx>
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
CatalogListModel::CatalogListModel(QObject* pr, simgear::pkg::RootRef& rootRef) :
|
||||
class CatalogDelegate : public simgear::pkg::Delegate
|
||||
{
|
||||
public:
|
||||
CatalogDelegate(CatalogListModel* outer) : p(outer) {}
|
||||
|
||||
void catalogRefreshed(CatalogRef catalog, StatusCode) override
|
||||
{
|
||||
p->onCatalogStatusChanged(catalog);
|
||||
}
|
||||
|
||||
void startInstall(InstallRef) override {}
|
||||
void installProgress(InstallRef, unsigned int, unsigned int) override {}
|
||||
void finishInstall(InstallRef, StatusCode ) override {}
|
||||
private:
|
||||
CatalogListModel* p = nullptr;
|
||||
};
|
||||
|
||||
|
||||
CatalogListModel::CatalogListModel(QObject* pr, const
|
||||
simgear::pkg::RootRef& rootRef) :
|
||||
QAbstractListModel(pr),
|
||||
m_packageRoot(rootRef)
|
||||
{
|
||||
refresh();
|
||||
m_delegate = new CatalogDelegate(this);
|
||||
m_packageRoot->addDelegate(m_delegate);
|
||||
|
||||
resetData();
|
||||
}
|
||||
|
||||
CatalogListModel::~CatalogListModel()
|
||||
{
|
||||
m_packageRoot->removeDelegate(m_delegate);
|
||||
}
|
||||
|
||||
void CatalogListModel::refresh()
|
||||
void CatalogListModel::resetData()
|
||||
{
|
||||
CatalogList updatedCatalogs = m_packageRoot->allCatalogs();
|
||||
std::sort(updatedCatalogs.begin(), updatedCatalogs.end(),
|
||||
[](const CatalogRef& catA, const CatalogRef& catB)
|
||||
{ // lexicographic ordering
|
||||
return catA->name() < catB->name();
|
||||
});
|
||||
|
||||
if (updatedCatalogs == m_catalogs)
|
||||
return;
|
||||
|
||||
beginResetModel();
|
||||
m_catalogs = m_packageRoot->allCatalogs();
|
||||
m_catalogs = updatedCatalogs;
|
||||
endResetModel();
|
||||
|
||||
emit catalogsChanged();
|
||||
}
|
||||
|
||||
int CatalogListModel::rowCount(const QModelIndex& parent) const
|
||||
|
@ -83,6 +120,10 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
|
|||
}
|
||||
}
|
||||
return tr("%1 - %2").arg(name).arg(desc);
|
||||
} else if (role == CatalogDescriptionRole) {
|
||||
return QString::fromStdString(cat->description());
|
||||
} else if (role == CatalogNameRole) {
|
||||
return QString::fromStdString(cat->name());
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
return QString::fromStdString(cat->url());
|
||||
} else if (role == CatalogUrlRole) {
|
||||
|
@ -93,6 +134,10 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
|
|||
return static_cast<quint32>(cat->packages().size());
|
||||
} else if (role == CatalogInstallCountRole) {
|
||||
return static_cast<quint32>(cat->installedPackages().size());
|
||||
} else if (role == CatalogStatusRole) {
|
||||
return translateStatusForCatalog(cat);
|
||||
} else if (role == CatalogIsNewlyAdded) {
|
||||
return (cat == m_newlyAddedCatalog);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
|
@ -112,3 +157,171 @@ Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const
|
|||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CatalogListModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
||||
result[CatalogUrlRole] = "url";
|
||||
result[CatalogIdRole] = "id";
|
||||
result[CatalogDescriptionRole] = "description";
|
||||
result[CatalogNameRole] = "name";
|
||||
result[CatalogStatusRole] = "status";
|
||||
result[CatalogIsNewlyAdded] = "isNewlyAdded";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CatalogListModel::removeCatalog(int index)
|
||||
{
|
||||
if ((index < 0) || (index >= m_catalogs.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string removeId = m_catalogs.at(index)->id();
|
||||
m_packageRoot->removeCatalogById(removeId);
|
||||
resetData();
|
||||
}
|
||||
|
||||
void CatalogListModel::refreshCatalog(int index)
|
||||
{
|
||||
if ((index < 0) || (index >= m_catalogs.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_catalogs.at(index)->refresh();
|
||||
}
|
||||
|
||||
void CatalogListModel::installDefaultCatalog()
|
||||
{
|
||||
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
|
||||
m_newlyAddedCatalog = Catalog::createFromUrl(m_packageRoot, http->getDefaultCatalogUrl());
|
||||
emit isAddingCatalogChanged();
|
||||
emit statusOfAddingCatalogChanged();
|
||||
resetData();
|
||||
}
|
||||
|
||||
void CatalogListModel::addCatalogByUrl(QUrl url)
|
||||
{
|
||||
if (m_newlyAddedCatalog) {
|
||||
qWarning() << Q_FUNC_INFO << "already adding a catalog";
|
||||
return;
|
||||
}
|
||||
|
||||
m_newlyAddedCatalog = Catalog::createFromUrl(m_packageRoot, url.toString().toStdString());
|
||||
resetData();
|
||||
emit isAddingCatalogChanged();
|
||||
}
|
||||
|
||||
int CatalogListModel::indexOf(QUrl url)
|
||||
{
|
||||
std::string urlString = url.toString().toStdString();
|
||||
auto it = std::find_if(m_catalogs.begin(), m_catalogs.end(),
|
||||
[urlString](simgear::pkg::CatalogRef cat) { return cat->url() == urlString;});
|
||||
if (it == m_catalogs.end())
|
||||
return -1;
|
||||
|
||||
return std::distance(m_catalogs.begin(), it);
|
||||
}
|
||||
|
||||
void CatalogListModel::finalizeAddCatalog()
|
||||
{
|
||||
if (!m_newlyAddedCatalog) {
|
||||
qWarning() << Q_FUNC_INFO << "no catalog add in progress";
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::find(m_catalogs.begin(), m_catalogs.end(), m_newlyAddedCatalog);
|
||||
if (it == m_catalogs.end()) {
|
||||
qWarning() << Q_FUNC_INFO << "couldn't find new catalog in m_catalogs" << QString::fromStdString(m_newlyAddedCatalog->url());
|
||||
return;
|
||||
}
|
||||
|
||||
const int row = std::distance(m_catalogs.begin(), it);
|
||||
m_newlyAddedCatalog.clear();
|
||||
emit isAddingCatalogChanged();
|
||||
emit statusOfAddingCatalogChanged();
|
||||
emit dataChanged(index(row), index(row));
|
||||
|
||||
}
|
||||
|
||||
void CatalogListModel::abandonAddCatalog()
|
||||
{
|
||||
if (!m_newlyAddedCatalog)
|
||||
return;
|
||||
|
||||
m_packageRoot->removeCatalog(m_newlyAddedCatalog);
|
||||
|
||||
m_newlyAddedCatalog.clear();
|
||||
emit isAddingCatalogChanged();
|
||||
emit statusOfAddingCatalogChanged();
|
||||
|
||||
resetData();
|
||||
}
|
||||
|
||||
bool CatalogListModel::isAddingCatalog() const
|
||||
{
|
||||
return m_newlyAddedCatalog.get() != nullptr;
|
||||
}
|
||||
|
||||
void CatalogListModel::onCatalogStatusChanged(Catalog* cat)
|
||||
{
|
||||
if (cat == nullptr) {
|
||||
resetData();
|
||||
return;
|
||||
}
|
||||
|
||||
//qInfo() << Q_FUNC_INFO << "for" << QString::fromStdString(cat->url()) << translateStatusForCatalog(cat);
|
||||
|
||||
// download the official catalog often fails with a 404 due to how we
|
||||
// compute the version-specific URL. This is the logic which bounces the UI
|
||||
// to the fallback URL.
|
||||
if (cat->status() == Delegate::FAIL_NOT_FOUND) {
|
||||
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
|
||||
if (cat->url() == http->getDefaultCatalogUrl()) {
|
||||
cat->setUrl(http->getDefaultCatalogFallbackUrl());
|
||||
cat->refresh(); // and trigger another refresh
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cat == m_newlyAddedCatalog) {
|
||||
// defer this signal slightly so that QML calling finalizeAdd or
|
||||
// abandonAdd in response, doesn't re-enter the package code
|
||||
QTimer::singleShot(0, this, &CatalogListModel::statusOfAddingCatalogChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::find(m_catalogs.begin(), m_catalogs.end(), cat);
|
||||
if (it == m_catalogs.end())
|
||||
return;
|
||||
|
||||
int row = std::distance(m_catalogs.begin(), it);
|
||||
emit dataChanged(index(row), index(row));
|
||||
}
|
||||
|
||||
CatalogListModel::CatalogStatus CatalogListModel::translateStatusForCatalog(CatalogRef cat) const
|
||||
{
|
||||
switch (cat->status()) {
|
||||
case Delegate::STATUS_SUCCESS:
|
||||
case Delegate::STATUS_REFRESHED:
|
||||
return Ok;
|
||||
|
||||
case Delegate::FAIL_DOWNLOAD: return NetworkError;
|
||||
case Delegate::STATUS_IN_PROGRESS: return Refreshing;
|
||||
case Delegate::FAIL_NOT_FOUND: return NotFoundOnServer;
|
||||
case Delegate::FAIL_VERSION: return IncomaptbleVersion;
|
||||
case Delegate::FAIL_HTTP_FORBIDDEN: return HTTPForbidden;
|
||||
case Delegate::FAIL_VALIDATION: return InvalidData;
|
||||
default:
|
||||
return UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
CatalogListModel::CatalogStatus CatalogListModel::statusOfAddingCatalog() const
|
||||
{
|
||||
if (!m_newlyAddedCatalog.get()) {
|
||||
return NoAddInProgress;
|
||||
}
|
||||
|
||||
return translateStatusForCatalog(m_newlyAddedCatalog);
|
||||
}
|
||||
|
|
|
@ -34,17 +34,24 @@ const int CatalogUrlRole = Qt::UserRole + 1;
|
|||
const int CatalogIdRole = Qt::UserRole + 2;
|
||||
const int CatalogPackageCountRole = Qt::UserRole + 3;
|
||||
const int CatalogInstallCountRole = Qt::UserRole + 4;
|
||||
const int CatalogStatusRole = Qt::UserRole + 5;
|
||||
const int CatalogDescriptionRole = Qt::UserRole + 6;
|
||||
const int CatalogNameRole = Qt::UserRole + 7;
|
||||
const int CatalogIsNewlyAdded = Qt::UserRole + 8;
|
||||
|
||||
class CatalogDelegate;
|
||||
|
||||
class CatalogListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool isAddingCatalog READ isAddingCatalog NOTIFY isAddingCatalogChanged)
|
||||
Q_PROPERTY(CatalogStatus statusOfAddingCatalog READ statusOfAddingCatalog NOTIFY statusOfAddingCatalogChanged)
|
||||
public:
|
||||
CatalogListModel(QObject* pr, simgear::pkg::RootRef& root);
|
||||
CatalogListModel(QObject* pr, const simgear::pkg::RootRef& root);
|
||||
|
||||
~CatalogListModel();
|
||||
|
||||
void refresh();
|
||||
|
||||
int rowCount(const QModelIndex& parent) const override;
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
|
@ -53,12 +60,64 @@ public:
|
|||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
private slots:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void removeCatalog(int index);
|
||||
|
||||
Q_INVOKABLE void refreshCatalog(int index);
|
||||
|
||||
Q_INVOKABLE void installDefaultCatalog();
|
||||
|
||||
// returns the index of the new catalog
|
||||
Q_INVOKABLE void addCatalogByUrl(QUrl url);
|
||||
|
||||
Q_INVOKABLE void finalizeAddCatalog();
|
||||
|
||||
Q_INVOKABLE void abandonAddCatalog();
|
||||
|
||||
bool isAddingCatalog() const;
|
||||
|
||||
enum CatalogStatus
|
||||
{
|
||||
Ok = 0,
|
||||
Refreshing,
|
||||
NetworkError,
|
||||
NotFoundOnServer,
|
||||
IncomaptbleVersion,
|
||||
HTTPForbidden,
|
||||
InvalidData,
|
||||
UnknownError,
|
||||
NoAddInProgress
|
||||
};
|
||||
|
||||
Q_ENUM(CatalogStatus)
|
||||
|
||||
void onCatalogStatusChanged(simgear::pkg::Catalog *cat);
|
||||
|
||||
CatalogStatus statusOfAddingCatalog() const;
|
||||
|
||||
signals:
|
||||
void isAddingCatalogChanged();
|
||||
void statusOfAddingCatalogChanged();
|
||||
|
||||
void catalogsChanged();
|
||||
|
||||
public slots:
|
||||
void resetData();
|
||||
|
||||
private slots:
|
||||
|
||||
private:
|
||||
simgear::pkg::RootRef m_packageRoot;
|
||||
simgear::pkg::CatalogList m_catalogs;
|
||||
|
||||
simgear::pkg::CatalogRef m_newlyAddedCatalog;
|
||||
|
||||
int indexOf(QUrl url);
|
||||
|
||||
CatalogDelegate* m_delegate;
|
||||
|
||||
CatalogStatus translateStatusForCatalog(simgear::pkg::CatalogRef cat) const;
|
||||
};
|
||||
|
||||
#endif // of FG_GUI_CATALOG_LIST_MODEL
|
||||
|
|
|
@ -297,7 +297,10 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
|
||||
<widget class="LocationWidget" name="location"/>
|
||||
|
||||
<widget class="QQuickWidget" name="environmentPage">
|
||||
</widget>
|
||||
<widget class="QWidget" name="newSettingsPage">
|
||||
|
@ -319,6 +322,33 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
|
||||
<widget class="QWidget" name="addOnsPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QQuickWidget" name="addOns"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
|
||||
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
|
||||
// remove me once location widget is ported to Quick
|
||||
#include "LocationWidget.hxx"
|
||||
#include "PathsDialog.hxx"
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
|
@ -152,7 +151,6 @@ void LauncherController::restoreSettings()
|
|||
m_serversModel->requestRestore();
|
||||
|
||||
emit summaryChanged();
|
||||
emit showNoOfficialHangarChanged();
|
||||
}
|
||||
|
||||
void LauncherController::saveSettings()
|
||||
|
@ -366,20 +364,8 @@ void LauncherController::downloadDirChanged(QString path)
|
|||
aircraftCache->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
aircraftCache->scanDirs();
|
||||
|
||||
emit showNoOfficialHangarChanged();
|
||||
|
||||
// re-set scenery dirs
|
||||
setSceneryPaths();
|
||||
}
|
||||
|
||||
bool LauncherController::shouldShowOfficialCatalogMessage() const
|
||||
{
|
||||
QSettings settings;
|
||||
bool showOfficialCatalogMesssage = !globals->get_subsystem<FGHTTPClient>()->isDefaultCatalogInstalled();
|
||||
if (settings.value("hide-official-catalog-message").toBool()) {
|
||||
showOfficialCatalogMesssage = false;
|
||||
}
|
||||
return showOfficialCatalogMesssage;
|
||||
flightgear::launcherSetSceneryPaths();
|
||||
}
|
||||
|
||||
QmlAircraftInfo *LauncherController::selectedAircraftInfo() const
|
||||
|
@ -483,14 +469,6 @@ simgear::pkg::PackageRef LauncherController::packageForAircraftURI(QUrl uri) con
|
|||
return globals->packageRoot()->getPackageById(ident.toStdString());
|
||||
}
|
||||
|
||||
void LauncherController::onAircraftPathsChanged()
|
||||
{
|
||||
QSettings settings;
|
||||
auto aircraftCache = LocalAircraftCache::instance();
|
||||
aircraftCache->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
aircraftCache->scanDirs();
|
||||
}
|
||||
|
||||
bool LauncherController::validateMetarString(QString metar)
|
||||
{
|
||||
if (metar.isEmpty()) {
|
||||
|
@ -561,11 +539,6 @@ void LauncherController::queryMPServers()
|
|||
m_serversModel->refresh();
|
||||
}
|
||||
|
||||
bool LauncherController::showNoOfficialHanger() const
|
||||
{
|
||||
return shouldShowOfficialCatalogMessage();
|
||||
}
|
||||
|
||||
QString LauncherController::versionString() const
|
||||
{
|
||||
return FLIGHTGEAR_VERSION;
|
||||
|
@ -618,25 +591,6 @@ void LauncherController::onAircraftInstallFailed(QModelIndex index, QString erro
|
|||
}
|
||||
|
||||
|
||||
void LauncherController::setSceneryPaths()
|
||||
{
|
||||
flightgear::launcherSetSceneryPaths();
|
||||
}
|
||||
|
||||
void LauncherController::officialCatalogAction(QString s)
|
||||
{
|
||||
if (s == "hide") {
|
||||
QSettings settings;
|
||||
settings.setValue("hide-official-catalog-message", true);
|
||||
} else if (s == "add-official") {
|
||||
// TEMPROARY, FIXME
|
||||
AddOnsPage::addDefaultCatalog(nullptr, false /* not silent */);
|
||||
}
|
||||
|
||||
emit showNoOfficialHangarChanged();
|
||||
}
|
||||
|
||||
|
||||
QPointF LauncherController::mapToGlobal(QQuickItem *item, const QPointF &pos) const
|
||||
{
|
||||
QPointF scenePos = item->mapToScene(pos);
|
||||
|
|
|
@ -43,8 +43,6 @@ class LauncherController : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool showNoOfficialHanger READ showNoOfficialHanger NOTIFY showNoOfficialHangarChanged)
|
||||
|
||||
Q_PROPERTY(AircraftProxyModel* installedAircraftModel MEMBER m_installedAircraftModel CONSTANT)
|
||||
Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel CONSTANT)
|
||||
Q_PROPERTY(AircraftProxyModel* searchAircraftModel MEMBER m_aircraftSearchModel CONSTANT)
|
||||
|
@ -92,10 +90,6 @@ public:
|
|||
|
||||
Q_INVOKABLE void queryMPServers();
|
||||
|
||||
bool showNoOfficialHanger() const;
|
||||
|
||||
Q_INVOKABLE void officialCatalogAction(QString s);
|
||||
|
||||
QUrl selectedAircraft() const;
|
||||
|
||||
// work around the fact, that this is not available on QQuickItem until 5.7
|
||||
|
@ -145,8 +139,6 @@ public:
|
|||
|
||||
bool canFly() const;
|
||||
|
||||
void setSceneryPaths();
|
||||
|
||||
AircraftItemModel* baseAircraftModel() const
|
||||
{ return m_aircraftModel; }
|
||||
|
||||
|
@ -154,7 +146,6 @@ public:
|
|||
void saveSettings();
|
||||
signals:
|
||||
|
||||
void showNoOfficialHangarChanged();
|
||||
void selectedAircraftChanged(QUrl selectedAircraft);
|
||||
|
||||
void searchChanged();
|
||||
|
@ -176,8 +167,6 @@ public slots:
|
|||
|
||||
void setEnvironmentSummary(QStringList environmentSummary);
|
||||
|
||||
void onAircraftPathsChanged();
|
||||
|
||||
private slots:
|
||||
|
||||
void onAircraftInstalledCompleted(QModelIndex index);
|
||||
|
@ -198,8 +187,6 @@ private:
|
|||
// scrolling, to give the view time it seems.
|
||||
void delayedAircraftModelReset();
|
||||
|
||||
bool shouldShowOfficialCatalogMessage() const;
|
||||
|
||||
void collectAircraftArgs();
|
||||
|
||||
private:
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <QMenu>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QStandardItemModel>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include <QQuickItem>
|
||||
|
@ -34,13 +33,17 @@
|
|||
|
||||
// launcher headers
|
||||
#include "QtLauncher.hxx"
|
||||
#include "PathsDialog.hxx"
|
||||
|
||||
#include "AircraftModel.hxx"
|
||||
#include "AircraftSearchFilterModel.hxx"
|
||||
#include "DefaultAircraftLocator.hxx"
|
||||
#include "LaunchConfig.hxx"
|
||||
#include "ViewCommandLinePage.hxx"
|
||||
#include "AircraftModel.hxx"
|
||||
#include "LocalAircraftCache.hxx"
|
||||
#include "LauncherController.hxx"
|
||||
#include "DefaultAircraftLocator.hxx"
|
||||
#include "AddOnsController.hxx"
|
||||
#include "CatalogListModel.hxx"
|
||||
|
||||
#include "ui_Launcher.h"
|
||||
|
||||
|
@ -107,18 +110,6 @@ LauncherMainWindow::LauncherMainWindow() :
|
|||
connect(qa, &QAction::triggered, this, &LauncherMainWindow::onQuit);
|
||||
addAction(qa);
|
||||
|
||||
|
||||
AddOnsPage* addOnsPage = new AddOnsPage(NULL, globals->packageRoot());
|
||||
connect(addOnsPage, &AddOnsPage::sceneryPathsChanged,
|
||||
m_controller, &LauncherController::setSceneryPaths);
|
||||
connect(addOnsPage, &AddOnsPage::aircraftPathsChanged,
|
||||
m_controller, &LauncherController::onAircraftPathsChanged);
|
||||
m_ui->stack->addWidget(addOnsPage);
|
||||
|
||||
connect(m_controller->baseAircraftModel(), &AircraftItemModel::catalogsRefreshed,
|
||||
addOnsPage, &AddOnsPage::onCatalogsRefreshed);
|
||||
|
||||
|
||||
m_viewCommandLinePage = new ViewCommandLinePage;
|
||||
m_viewCommandLinePage->setLaunchConfig(m_controller->config());
|
||||
m_ui->stack->addWidget(m_viewCommandLinePage);
|
||||
|
@ -127,7 +118,9 @@ LauncherMainWindow::LauncherMainWindow() :
|
|||
restoreGeometry(settings.value("window-geometry").toByteArray());
|
||||
|
||||
m_controller->restoreSettings();
|
||||
restoreSettings();
|
||||
flightgear::launcherSetSceneryPaths();
|
||||
|
||||
auto addOnsCtl = new AddOnsController(this);
|
||||
|
||||
////////////
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -144,6 +137,7 @@ LauncherMainWindow::LauncherMainWindow() :
|
|||
|
||||
m_ui->aircraftList->engine()->addImportPath("qrc:///");
|
||||
m_ui->aircraftList->engine()->rootContext()->setContextProperty("_launcher", m_controller);
|
||||
m_ui->aircraftList->engine()->rootContext()->setContextProperty("_addOns", addOnsCtl);
|
||||
|
||||
connect( m_ui->aircraftList, &QQuickWidget::statusChanged,
|
||||
this, &LauncherMainWindow::onQuickStatusChanged);
|
||||
|
@ -184,17 +178,18 @@ LauncherMainWindow::LauncherMainWindow() :
|
|||
connect( m_ui->summary, &QQuickWidget::statusChanged,
|
||||
this, &LauncherMainWindow::onQuickStatusChanged);
|
||||
m_ui->summary->setSource(QUrl("qrc:///qml/Summary.qml"));
|
||||
|
||||
|
||||
// addOns
|
||||
m_ui->addOns->engine()->addImportPath("qrc:///");
|
||||
m_ui->addOns->engine()->rootContext()->setContextProperty("_launcher", m_controller);
|
||||
m_ui->addOns->engine()->rootContext()->setContextProperty("_addOns", addOnsCtl);
|
||||
m_ui->addOns->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
m_ui->addOns->setSource(QUrl("qrc:///qml/AddOns.qml"));
|
||||
//////////////////////////
|
||||
|
||||
}
|
||||
|
||||
void LauncherMainWindow::restoreSettings()
|
||||
{
|
||||
if (!m_inAppMode) {
|
||||
m_controller->setSceneryPaths();
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherMainWindow::saveSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
|
@ -363,5 +358,3 @@ void LauncherMainWindow::onChangeDataDir()
|
|||
flightgear::restartTheApp();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
44
src/GUI/qml/AddButton.qml
Normal file
44
src/GUI/qml/AddButton.qml
Normal file
|
@ -0,0 +1,44 @@
|
|||
import QtQuick 2.4
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool enabled: true
|
||||
signal clicked
|
||||
|
||||
width: label.implicitWidth + height + Style.margin
|
||||
height: icon.implicitHeight + 1
|
||||
radius: Style.roundRadius
|
||||
|
||||
color: enabled ? (mouse.containsMouse ? Style.activeColor : Style.themeColor) : Style.disabledThemeColor
|
||||
|
||||
Text {
|
||||
id: label
|
||||
text: qsTr("Add")
|
||||
color: "white"
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: Style.margin
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
anchors.right: parent.right
|
||||
source: "qrc:///add-icon"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
anchors.fill: parent
|
||||
enabled: root.enabled
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
165
src/GUI/qml/AddCatalogPanel.qml
Normal file
165
src/GUI/qml/AddCatalogPanel.qml
Normal file
|
@ -0,0 +1,165 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear.Launcher 1.0 as FG
|
||||
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: addCatalogPanel
|
||||
state: "start"
|
||||
height: column.height
|
||||
|
||||
readonly property bool isActive: (state != "start")
|
||||
Behavior on height {
|
||||
NumberAnimation { duration: 200; }
|
||||
}
|
||||
|
||||
Column {
|
||||
id: column
|
||||
width: parent.width
|
||||
spacing: Style.margin
|
||||
|
||||
ClickableText {
|
||||
id: addCatalogPromptText
|
||||
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onClicked: {
|
||||
addCatalogPanel.state = "enter-url";
|
||||
}
|
||||
}
|
||||
|
||||
LineEdit {
|
||||
id: addCatalogUrlEntry
|
||||
width: parent.width
|
||||
useFullWidth: true
|
||||
label: qsTr("Hangar URL:");
|
||||
placeholder: "http://www.flightgear.org/hangars/some-catalog.xml"
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttonBox
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
|
||||
Button {
|
||||
id: addCatalogCancelButton
|
||||
anchors.right: addCatalogProceedButton.left
|
||||
anchors.rightMargin: Style.margin
|
||||
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
_addOns.catalogs.abandonAddCatalog();
|
||||
addCatalogPanel.state = "start";
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: addCatalogProceedButton
|
||||
anchors.right: parent.right
|
||||
|
||||
text: qsTr("Add hangar")
|
||||
|
||||
// todo check for a well-formed URL
|
||||
enabled: addCatalogUrlEntry.text !== ""
|
||||
|
||||
onClicked: {
|
||||
// this occurs in the retry case; abandon the current
|
||||
// add before starting the new one
|
||||
if (_addOns.catalogs.isAddingCatalog) {
|
||||
_addOns.catalogs.abandonAddCatalog();
|
||||
}
|
||||
|
||||
_addOns.catalogs.addCatalogByUrl(addCatalogUrlEntry.text)
|
||||
addCatalogPanel.state = "add-in-progress"
|
||||
}
|
||||
}
|
||||
} // of button box
|
||||
} // of column layout
|
||||
|
||||
Connections {
|
||||
target: _addOns.catalogs
|
||||
onStatusOfAddingCatalogChanged: {
|
||||
var status = _addOns.catalogs.statusOfAddingCatalog;
|
||||
if (status == FG.CatalogListModel.Refreshing) {
|
||||
// this can happen when adding the default catalog,
|
||||
// ensure our state matches
|
||||
addCatalogPanel.state = "add-in-progress"
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == FG.CatalogListModel.NoAddInProgress) {
|
||||
addCatalogPanel.state = "start";
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == FG.CatalogListModel.Ok) {
|
||||
// success, we are done
|
||||
_addOns.catalogs.finalizeAddCatalog();
|
||||
addCatalogPanel.state = "start";
|
||||
return;
|
||||
}
|
||||
|
||||
// if we reach this point, we're in an error situation, so
|
||||
// handle that
|
||||
addCatalogPanel.state = "add-failed-retry";
|
||||
}
|
||||
}
|
||||
|
||||
function addErrorMessage()
|
||||
{
|
||||
switch (_addOns.catalogs.statusOfAddingCatalog) {
|
||||
case FG.CatalogListModel.NotFoundOnServer:
|
||||
return qsTr("Failed to find a hangar description at the URL: '%1'. " +
|
||||
"Check you entered the URL correctly.");
|
||||
case FG.CatalogListModel.HTTPForbidden:
|
||||
return qsTr("Access to the hangar data was forbidden by the server. " +
|
||||
"Please check the URL you entered, or contact the hangar authors.");
|
||||
case FG.CatalogListModel.NetworkError:
|
||||
return qsTr("Failed to download from the server due to a network problem. " +
|
||||
"Check your Internet connection is working, and that you entered the correct URL.");
|
||||
case FG.CatalogListModel.IncomaptbleVersion:
|
||||
return qsTr("The hangar you requested is for a different version of FlightGear. "
|
||||
+ "(This is version %1)").arg(_launcher.versionString);
|
||||
|
||||
case FG.CatalogListModel.InvalidData:
|
||||
return qsTr("The requested hangar seems to be invalid (damaged or incomplete). "
|
||||
+ "Please contact the hangar authors, or try again later");
|
||||
|
||||
default:
|
||||
return qsTr("Unknown error: " + _addOns.catalogs.statusOfAddingCatalog);
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "start"
|
||||
PropertyChanges { target: addCatalogPromptText; text: qsTr("Click here to add a new aircraft hangar. (Note this requires an Internet connection)"); clickable: true }
|
||||
PropertyChanges { target: addCatalogUrlEntry; visible: false; text: "" } // reset URL on cancel / success
|
||||
PropertyChanges { target: buttonBox; visible: false }
|
||||
},
|
||||
|
||||
State {
|
||||
name: "enter-url"
|
||||
PropertyChanges { target: addCatalogPromptText; text: qsTr("Enter a hangar location (URL) to add."); clickable: false }
|
||||
PropertyChanges { target: addCatalogUrlEntry; visible: true }
|
||||
PropertyChanges { target: buttonBox; visible: true }
|
||||
},
|
||||
|
||||
State {
|
||||
name: "add-in-progress"
|
||||
PropertyChanges { target: addCatalogPromptText; text: qsTr("Retrieving hangar information..."); clickable: false }
|
||||
PropertyChanges { target: addCatalogUrlEntry; visible: false }
|
||||
PropertyChanges { target: addCatalogProceedButton; enabled: false }
|
||||
},
|
||||
|
||||
|
||||
State {
|
||||
name: "add-failed-retry"
|
||||
PropertyChanges { target: addCatalogPromptText; text: qsTr("There was a problem adding the hangar: %1.").arg(addErrorMessage()); clickable: false }
|
||||
PropertyChanges { target: addCatalogUrlEntry; visible: true }
|
||||
PropertyChanges { target: addCatalogProceedButton; enabled: true }
|
||||
}
|
||||
]
|
||||
}
|
409
src/GUI/qml/AddOns.qml
Normal file
409
src/GUI/qml/AddOns.qml
Normal file
|
@ -0,0 +1,409 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear.Launcher 1.0
|
||||
import "."
|
||||
|
||||
Flickable {
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: contents.childrenRect.height
|
||||
|
||||
interactive: false
|
||||
|
||||
Component {
|
||||
id: catalogDelegate
|
||||
|
||||
Rectangle {
|
||||
id: delegateRoot
|
||||
|
||||
// don't show the delegate for newly added catalogs, until the
|
||||
// adding process is complete
|
||||
visible: !model.isNewlyAdded
|
||||
|
||||
height: catalogTextColumn.childrenRect.height
|
||||
border.width: 1
|
||||
border.color: Style.minorFrameColor
|
||||
width: catalogsColumn.width
|
||||
|
||||
|
||||
Column {
|
||||
id: catalogTextColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: catalogDeleteButton.left
|
||||
anchors.rightMargin: Style.margin
|
||||
spacing: Style.margin
|
||||
|
||||
Text {
|
||||
font.pixelSize: Style.subHeadingFontPixelSize
|
||||
font.bold: true
|
||||
width: parent.width
|
||||
text: model.name
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: model.status === CatalogListModel.Ok
|
||||
width: parent.width
|
||||
text: model.description
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ClickableText {
|
||||
visible: model.status !== CatalogListModel.Ok
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("This hangar is currently disabled due to a problem. " +
|
||||
"Click here to try updating the hangar information from the server. "
|
||||
+ "(An Internet connection is required for this)");
|
||||
onClicked: {
|
||||
_addOns.catalogs.refreshCatalog(model.index)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: model.url
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DeleteButton {
|
||||
id: catalogDeleteButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: delegateHover.containsMouse
|
||||
|
||||
onClicked: {
|
||||
confirmDeleteHangar.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: delegateHover
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
YesNoPanel {
|
||||
id: confirmDeleteHangar
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
|
||||
yesText: qsTr("Remove")
|
||||
noText: qsTr("Cancel")
|
||||
yesIsDestructive: true
|
||||
promptText: qsTr("Remove this hangar? (Downloaded aircraft will be deleted from your computer)");
|
||||
onAccepted: {
|
||||
_addOns.catalogs.removeCatalog(model.index);
|
||||
}
|
||||
|
||||
onRejected: {
|
||||
confirmDeleteHangar.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contents
|
||||
width: parent.width - (Style.margin * 2)
|
||||
x: Style.margin
|
||||
spacing: Style.margin
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// catalogs //////////////////////////////////////////////////////
|
||||
|
||||
Item {
|
||||
id: catalogHeaderItem
|
||||
width: parent.width
|
||||
height: catalogHeadingText.height + catalogDescriptionText.height + Style.margin
|
||||
|
||||
Text {
|
||||
id: catalogHeadingText
|
||||
text: qsTr("Aircraft hangars")
|
||||
font.pixelSize: Style.headingFontPixelSize
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Text {
|
||||
id: catalogDescriptionText
|
||||
text: qsTr("Aircraft hangars are managed collections of aircraft, which can be " +
|
||||
"downloaded, installed and updated inside FlightGear.")
|
||||
anchors {
|
||||
top: catalogHeadingText.bottom
|
||||
topMargin: Style.margin
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
|
||||
} // of catalogs header item
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: catalogsColumn.childrenRect.height + Style.margin * 2
|
||||
border.width: 1
|
||||
border.color: Style.frameColor
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: catalogsColumn
|
||||
width: parent.width - Style.margin * 2
|
||||
x: Style.margin
|
||||
y: Style.margin
|
||||
spacing: Style.margin
|
||||
|
||||
Repeater {
|
||||
id: catalogsRepeater
|
||||
model: _addOns.catalogs
|
||||
delegate: catalogDelegate
|
||||
}
|
||||
|
||||
ClickableText {
|
||||
visible: !_addOns.isOfficialHangarRegistered && !addCatalogPanel.isActive
|
||||
width: parent.width
|
||||
text : qsTr("The official FlightGear aircraft hangar is not set up. To add it, click here.");
|
||||
onClicked: {
|
||||
_addOns.catalogs.installDefaultCatalog()
|
||||
}
|
||||
}
|
||||
|
||||
AddCatalogPanel {
|
||||
id: addCatalogPanel
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
Item {
|
||||
// spacing item
|
||||
width: parent.width
|
||||
height: Style.margin * 2
|
||||
}
|
||||
|
||||
Item {
|
||||
id: aircraftHeaderItem
|
||||
width: parent.width
|
||||
height: aircraftHeading.height + aircraftDescriptionText.height + Style.margin
|
||||
|
||||
Text {
|
||||
id: aircraftHeading
|
||||
text: qsTr("Additional aircraft folders")
|
||||
font.pixelSize: Style.headingFontPixelSize
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: addAircraftPathButton.left
|
||||
rightMargin: Style.margin
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: aircraftDescriptionText
|
||||
text: qsTr("To use aircraft you download yourself, FlightGear needs to " +
|
||||
"know the folder(s) containing the aircraft data.")
|
||||
anchors {
|
||||
top: aircraftHeading.bottom
|
||||
topMargin: Style.margin
|
||||
left: parent.left
|
||||
right: addAircraftPathButton.left
|
||||
rightMargin: Style.margin
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
AddButton {
|
||||
id: addAircraftPathButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
var newPath =_addOns.addAircraftPath();
|
||||
if (newPath != "") {
|
||||
_addOns.aircraftPaths.push(newPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} // of aircraft header item
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: aircraftPathsColumn.childrenRect.height + 1
|
||||
border.width: 1
|
||||
border.color: Style.frameColor
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: aircraftPathsColumn
|
||||
width: parent.width - Style.margin * 2
|
||||
x: Style.margin
|
||||
|
||||
Repeater {
|
||||
id: aircraftPathsRepeater
|
||||
model: _addOns.aircraftPaths
|
||||
delegate: PathListDelegate {
|
||||
width: aircraftPathsColumn.width
|
||||
deletePromptText: qsTr("Remove the aircraft folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData);
|
||||
|
||||
onPerformDelete: {
|
||||
var modifiedPaths = _addOns.aircraftPaths.slice()
|
||||
modifiedPaths.splice(model.index, 1);
|
||||
_addOns.aircraftPaths = modifiedPaths;
|
||||
}
|
||||
|
||||
onPerformMove: {
|
||||
var modifiedPaths = _addOns.aircraftPaths.slice()
|
||||
modifiedPaths.splice(model.index, 1);
|
||||
modifiedPaths.splice(newIndex, 0, modelData)
|
||||
_addOns.aircraftPaths = modifiedPaths;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: (aircraftPathsRepeater.count == 0)
|
||||
width: parent.width
|
||||
text : qsTr("No custom aircraft paths are configured.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
Item {
|
||||
// spacing item
|
||||
width: parent.width
|
||||
height: Style.margin * 2
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sceneryHeaderItem
|
||||
width: parent.width
|
||||
height: sceneryHeading.height + sceneryDescriptionText.height + Style.margin
|
||||
|
||||
Text {
|
||||
id: sceneryHeading
|
||||
text: qsTr("Additional scenery folders")
|
||||
font.pixelSize: Style.headingFontPixelSize
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: addSceneryPathButton.left
|
||||
rightMargin: Style.margin
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sceneryDescriptionText
|
||||
text: qsTr("To use scenery you download yourself, FlightGear needs " +
|
||||
"to know the folders containing the scenery data. " +
|
||||
"Adjust the order of the list to control which scenery is used in a region.");
|
||||
anchors {
|
||||
top: sceneryHeading.bottom
|
||||
topMargin: Style.margin
|
||||
left: parent.left
|
||||
right: addSceneryPathButton.left
|
||||
rightMargin: Style.margin
|
||||
}
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
AddButton {
|
||||
id: addSceneryPathButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
var newPath =_addOns.addSceneryPath();
|
||||
if (newPath !== "") {
|
||||
_addOns.sceneryPaths.push(newPath)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} // of aircraft header item
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: sceneryPathsColumn.childrenRect.height + 1
|
||||
border.width: 1
|
||||
border.color: Style.frameColor
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: sceneryPathsColumn
|
||||
width: parent.width - Style.margin * 2
|
||||
x: Style.margin
|
||||
|
||||
Repeater {
|
||||
id: sceneryPathsRepeater
|
||||
model: _addOns.sceneryPaths
|
||||
|
||||
delegate: PathListDelegate {
|
||||
width: sceneryPathsColumn.width
|
||||
deletePromptText: qsTr("Remove the scenery folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData);
|
||||
|
||||
onPerformDelete: {
|
||||
var modifiedPaths = _addOns.sceneryPaths.slice()
|
||||
modifiedPaths.splice(model.index, 1);
|
||||
_addOns.sceneryPaths = modifiedPaths;
|
||||
}
|
||||
|
||||
onPerformMove: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: (sceneryPathsRepeater.count == 0)
|
||||
width: parent.width
|
||||
text : qsTr("No custom scenery paths are configured.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(installTarballText.implicitHeight, installTarballButton.height)
|
||||
Button {
|
||||
id: installTarballButton
|
||||
text: "Install add-on scenery"
|
||||
|
||||
onClicked: {
|
||||
var path = _addOns.installCustomScenery();
|
||||
if (path != "") {
|
||||
// insert into scenery paths if not already present
|
||||
for (var p in _addOns.sceneryPaths) {
|
||||
if (p === path)
|
||||
return; // found, we are are done
|
||||
}
|
||||
|
||||
// not found, add it
|
||||
_addOns.sceneryPaths.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: installTarballText
|
||||
anchors {
|
||||
left: installTarballButton.right
|
||||
right: parent.right
|
||||
leftMargin: Style.margin
|
||||
}
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("If you have downloaded scenery manually from the official FlightGear website, " +
|
||||
"you can use this button to extract and install it into a suitable folder. " +
|
||||
"(Scenery downloaded this way should have a file name such as 'w40n020.tar.gz')"
|
||||
)
|
||||
}
|
||||
} // of install-tarbal item
|
||||
} // of column
|
||||
|
||||
}
|
|
@ -184,7 +184,7 @@ Item
|
|||
PropertyChanges {
|
||||
target: aircraftList
|
||||
model: _launcher.browseAircraftModel
|
||||
header: _launcher.showNoOfficialHanger ? noDefaultCatalogHeader : ratingsHeader
|
||||
header: _addOns.showNoOfficialHangar ? noDefaultCatalogHeader : ratingsHeader
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -7,14 +7,16 @@ Rectangle {
|
|||
property string text
|
||||
property string hoverText: ""
|
||||
property bool enabled: true
|
||||
property bool destructiveAction: false
|
||||
|
||||
readonly property string __baseColor: destructiveAction ? Style.destructiveActionColor : Style.themeColor
|
||||
signal clicked
|
||||
|
||||
width: Style.strutSize * 2
|
||||
width: Math.max(Style.strutSize * 2, buttonText.implicitWidth + radius * 2)
|
||||
height: buttonText.implicitHeight + (radius * 2)
|
||||
radius: Style.roundRadius
|
||||
|
||||
color: enabled ? (mouse.containsMouse ? Style.activeColor : Style.themeColor) : Style.disabledThemeColor
|
||||
color: enabled ? (mouse.containsMouse ? Style.activeColor : __baseColor) : Style.disabledThemeColor
|
||||
|
||||
Text {
|
||||
id: buttonText
|
||||
|
|
|
@ -2,18 +2,22 @@ import QtQuick 2.4
|
|||
import "."
|
||||
|
||||
Text {
|
||||
id: root
|
||||
signal clicked();
|
||||
|
||||
property bool clickable: true
|
||||
property color baseTextColor: Style.baseTextColor
|
||||
color: mouse.containsMouse ? Style.themeColor : baseTextColor
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
hoverEnabled: true
|
||||
|
||||
enabled: root.clickable
|
||||
hoverEnabled: root.clickable
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: parent.clicked();
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
cursorShape: root.clickable ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
|
30
src/GUI/qml/DeleteButton.qml
Normal file
30
src/GUI/qml/DeleteButton.qml
Normal file
|
@ -0,0 +1,30 @@
|
|||
import QtQuick 2.4
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool enabled: true
|
||||
signal clicked
|
||||
|
||||
width: height
|
||||
height: icon.implicitHeight + 1
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
x: 0
|
||||
y: 0
|
||||
source: "qrc:///cancel-icon"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
anchors.fill: parent
|
||||
enabled: root.enabled
|
||||
|
||||
onClicked: {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
37
src/GUI/qml/DragToReorderButton.qml
Normal file
37
src/GUI/qml/DragToReorderButton.qml
Normal file
|
@ -0,0 +1,37 @@
|
|||
import QtQuick 2.4
|
||||
|
||||
Item {
|
||||
|
||||
width: height
|
||||
height: icon.implicitHeight + 1
|
||||
|
||||
signal moveUp();
|
||||
signal moveDown();
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
x: 0
|
||||
y: 0
|
||||
source: "qrc:///reorder-list-icon"
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: moveUpArea
|
||||
height: parent.height / 2
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
parent.moveUp();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: moveDownArea
|
||||
height: parent.height / 2
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: {
|
||||
parent.moveDown();
|
||||
}
|
||||
}
|
||||
}
|
67
src/GUI/qml/LineEdit.qml
Normal file
67
src/GUI/qml/LineEdit.qml
Normal file
|
@ -0,0 +1,67 @@
|
|||
import QtQuick 2.4
|
||||
import "."
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
property string label
|
||||
property string placeholder: ""
|
||||
property alias validator: edit.validator
|
||||
property alias text: edit.text
|
||||
|
||||
property alias suggestedWidthString: metrics.text
|
||||
readonly property int suggestedWidth: useFullWidth ? root.width
|
||||
: ((metrics.width == 0) ? Style.strutSize * 4
|
||||
: metrics.width)
|
||||
|
||||
property bool useFullWidth: false
|
||||
|
||||
implicitHeight: editFrame.height
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
}
|
||||
|
||||
Text {
|
||||
id: label
|
||||
text: root.label
|
||||
anchors.verticalCenter: editFrame.verticalCenter
|
||||
color: editFrame.activeFocus ? Style.themeColor :
|
||||
(root.enabled ? "black" : Style.inactiveThemeColor)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: editFrame
|
||||
|
||||
anchors.left: label.right
|
||||
anchors.margins: Style.margin
|
||||
|
||||
|
||||
height: edit.implicitHeight + Style.margin
|
||||
|
||||
width: Math.min(root.width - (label.width + Style.margin * 2), Math.max(suggestedWidth, edit.implicitWidth));
|
||||
|
||||
radius: Style.roundRadius
|
||||
border.color: edit.activeFocus ? Style.frameColor : Style.minorFrameColor
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
TextInput {
|
||||
id: edit
|
||||
enabled: root.enabled
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.margin
|
||||
selectByMouse: true
|
||||
focus: true
|
||||
|
||||
Text {
|
||||
id: placeholder
|
||||
visible: (edit.text.length == 0) && !edit.activeFocus
|
||||
text: root.placeholder
|
||||
color: Style.baseTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -30,7 +30,9 @@ Rectangle {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onClicked: {
|
||||
_launcher.officialCatalogAction("add-official");
|
||||
console.warn("Implement me")
|
||||
|
||||
// _launcher.officialCatalogAction("add-official");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +42,7 @@ Rectangle {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onClicked: {
|
||||
_launcher.officialCatalogAction("hide");
|
||||
_addOns.officialCatalogAction("hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
107
src/GUI/qml/PathListDelegate.qml
Normal file
107
src/GUI/qml/PathListDelegate.qml
Normal file
|
@ -0,0 +1,107 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear.Launcher 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: delegateRoot
|
||||
|
||||
signal performDelete();
|
||||
signal performMove(var newIndex);
|
||||
|
||||
property alias deletePromptText: confirmDeletePath.promptText
|
||||
|
||||
height: childrenRect.height
|
||||
|
||||
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
|
||||
// color: "#cfcfcf"
|
||||
|
||||
height: Math.max(label.implicitHeight, pathDeleteButton.height)
|
||||
width: delegateRoot.width
|
||||
|
||||
MouseArea {
|
||||
id: pathDelegateHover
|
||||
anchors.fill: parent
|
||||
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: modelData
|
||||
onClicked: {
|
||||
// open the location
|
||||
_addOns.openDirectory(modelData)
|
||||
}
|
||||
anchors.left: parent.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: pathDelegateHover.containsMouse
|
||||
onClicked: {
|
||||
confirmDeletePath.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
DragToReorderButton {
|
||||
id: reorderButton
|
||||
anchors.right: pathDeleteButton.left
|
||||
anchors.rightMargin: Style.margin
|
||||
visible: pathDelegateHover.containsMouse
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,17 +5,11 @@ SettingControl {
|
|||
id: root
|
||||
implicitHeight: childrenRect.height
|
||||
|
||||
property string placeholder: ""
|
||||
property alias placeholder: edit.placeholder
|
||||
property alias validation: edit.validator
|
||||
property alias value: edit.text
|
||||
|
||||
property alias suggestedWidthString: metrics.text
|
||||
readonly property int suggestedWidth: useFullWidth ? root.width
|
||||
: ((metrics.width == 0) ? Style.strutSize * 4
|
||||
: metrics.width)
|
||||
|
||||
property bool useFullWidth: false
|
||||
|
||||
property alias suggestedWidthString: edit.suggestedWidthString
|
||||
property alias useFullWidth: edit.useFullWidth
|
||||
property string defaultValue: ""
|
||||
|
||||
function apply()
|
||||
|
@ -25,57 +19,17 @@ SettingControl {
|
|||
}
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
}
|
||||
|
||||
Text {
|
||||
id: label
|
||||
text: root.label
|
||||
anchors.verticalCenter: editFrame.verticalCenter
|
||||
color: editFrame.activeFocus ? Style.themeColor :
|
||||
(root.enabled ? "black" : Style.inactiveThemeColor)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: editFrame
|
||||
|
||||
anchors.left: label.right
|
||||
anchors.margins: Style.margin
|
||||
|
||||
|
||||
height: edit.implicitHeight + Style.margin
|
||||
|
||||
width: Math.min(root.width - (label.width + Style.margin * 2), Math.max(suggestedWidth, edit.implicitWidth));
|
||||
|
||||
radius: Style.roundRadius
|
||||
border.color: edit.activeFocus ? Style.frameColor : Style.minorFrameColor
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
TextInput {
|
||||
id: edit
|
||||
enabled: root.enabled
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.margin
|
||||
selectByMouse: true
|
||||
|
||||
Text {
|
||||
id: placeholder
|
||||
visible: (edit.text.length == 0) && !edit.activeFocus
|
||||
text: root.placeholder
|
||||
color: Style.baseTextColor
|
||||
}
|
||||
}
|
||||
LineEdit {
|
||||
id: edit
|
||||
label: root.label
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
SettingDescription {
|
||||
id: description
|
||||
enabled: root.enabled
|
||||
text: root.description
|
||||
anchors.top: editFrame.bottom
|
||||
anchors.top: edit.bottom
|
||||
anchors.topMargin: Style.margin
|
||||
width: parent.width
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ QtObject
|
|||
readonly property string minorFrameColor: "#9f9f9f"
|
||||
|
||||
readonly property string themeColor: "#1b7ad3"
|
||||
readonly property string destructiveActionColor: "#c62703"
|
||||
|
||||
readonly property string activeColor: Qt.darker(themeColor)
|
||||
|
||||
readonly property string inactiveThemeColor: "#9f9f9f"
|
||||
|
@ -21,6 +23,7 @@ QtObject
|
|||
readonly property string baseTextColor: "#3f3f3f"
|
||||
|
||||
readonly property int baseFontPixelSize: 12
|
||||
readonly property int subHeadingFontPixelSize: 14
|
||||
readonly property int headingFontPixelSize: 18
|
||||
|
||||
readonly property string disabledTextColor: "#5f5f5f"
|
||||
|
|
53
src/GUI/qml/YesNoPanel.qml
Normal file
53
src/GUI/qml/YesNoPanel.qml
Normal file
|
@ -0,0 +1,53 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Rectangle {
|
||||
property alias promptText:prompt.text
|
||||
property alias yesText: yesButton.text
|
||||
property alias noText: noButton.text
|
||||
|
||||
// change UI appearance if yes is a destructive action
|
||||
property bool yesIsDestructive: false
|
||||
|
||||
signal accepted()
|
||||
|
||||
signal rejected()
|
||||
|
||||
Text {
|
||||
id: prompt
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.left
|
||||
right: yesButton.left
|
||||
margins: Style.margin
|
||||
}
|
||||
|
||||
height: parent.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Button {
|
||||
id: yesButton
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: noButton.left
|
||||
rightMargin: Style.margin
|
||||
}
|
||||
|
||||
destructiveAction: parent.yesIsDestructive
|
||||
onClicked: parent.accepted()
|
||||
}
|
||||
|
||||
Button {
|
||||
id: noButton
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
rightMargin: Style.margin
|
||||
}
|
||||
|
||||
onClicked: parent.rejected();
|
||||
}
|
||||
|
||||
}
|
BIN
src/GUI/qml/icons8-cancel-50.png
Normal file
BIN
src/GUI/qml/icons8-cancel-50.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
src/GUI/qml/icons8-drag-reorder-filled-50.png
Normal file
BIN
src/GUI/qml/icons8-drag-reorder-filled-50.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 468 B |
BIN
src/GUI/qml/icons8-plus-26.png
Normal file
BIN
src/GUI/qml/icons8-plus-26.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 413 B |
|
@ -73,6 +73,17 @@
|
|||
<file>qml/SettingsDateTimePicker.qml</file>
|
||||
<file>qml/Summary.qml</file>
|
||||
<file>qml/HistoryPopup.qml</file>
|
||||
<file>qml/AddOns.qml</file>
|
||||
<file>qml/DragToReorderButton.qml</file>
|
||||
<file>qml/AddButton.qml</file>
|
||||
<file alias="cancel-icon">qml/icons8-cancel-50.png</file>
|
||||
<file alias="reorder-list-icon">qml/icons8-drag-reorder-filled-50.png</file>
|
||||
<file alias="add-icon">qml/icons8-plus-26.png</file>
|
||||
<file>qml/DeleteButton.qml</file>
|
||||
<file>qml/YesNoPanel.qml</file>
|
||||
<file>qml/PathListDelegate.qml</file>
|
||||
<file>qml/AddCatalogPanel.qml</file>
|
||||
<file>qml/LineEdit.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/preview">
|
||||
<file alias="close-icon">preview-close.png</file>
|
||||
|
|
Loading…
Add table
Reference in a new issue