1
0
Fork 0

Launcher: basic aircraft state selection

Automatic selection in the launcher is disabled for now, since
it needs more testing before release, but the basic UI for selection
is straightforward enough to throw in.
This commit is contained in:
James Turner 2018-02-11 21:28:25 +00:00
parent a05cdce793
commit 234320fe9d
7 changed files with 438 additions and 85 deletions

View file

@ -251,23 +251,59 @@
<number>0</number>
</property>
<widget class="QWidget" name="summaryPage">
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0,0,0,0,1" columnstretch="0,0,0,1,0">
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0" columnstretch="0,0,0,1,0">
<item row="0" column="3" colspan="2">
<widget class="QLabel" name="appTitleLabel">
<property name="font">
<font>
<pointsize>48</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>FlightGear 2017.1.0</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QPushButton" name="aircraftHistory">
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="stateLabel">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Location:</string>
<string>State:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="2">
<item row="3" column="3">
<widget class="QLabel" name="aircraftName">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>aircraft</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLabel" name="label_5">
<property name="font">
<font>
@ -282,13 +318,6 @@
</property>
</widget>
</item>
<item row="5" column="4">
<widget class="QPushButton" name="locationHistory">
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_4">
<property name="font">
@ -304,14 +333,41 @@
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QPushButton" name="aircraftHistory">
<property name="autoDefault">
<bool>false</bool>
<item row="7" column="3">
<widget class="QLabel" name="locationDescription">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>location</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="3" colspan="2">
<item row="2" column="3" colspan="2">
<widget class="QLabel" name="thumbnail">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>171</width>
<height>128</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="11" column="3" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -339,37 +395,55 @@
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QLabel" name="locationDescription">
<item row="5" column="3" colspan="2">
<widget class="QComboBox" name="stateCombo"/>
</item>
<item row="1" column="3" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;©2017 FlightGear contributors. Licensed under the GNU Public License. See &lt;a href=&quot;http://www.flightgear.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;here&lt;/span&gt;&lt;/a&gt; for more information&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>location</string>
<string>Location:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLabel" name="aircraftName">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<item row="0" column="2" rowspan="2">
<widget class="QLabel" name="logoIcon">
<property name="text">
<string>aircraft</string>
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
<property name="pixmap">
<pixmap resource="resources.qrc">:/app-icon-large</pixmap>
</property>
</widget>
</item>
<item row="6" column="3" colspan="2">
<item row="7" column="4">
<widget class="QPushButton" name="locationHistory">
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item row="8" column="3" colspan="2">
<widget class="QLabel" name="settingsDescription">
<property name="font">
<font>
@ -387,59 +461,15 @@
</property>
</widget>
</item>
<item row="2" column="3" colspan="2">
<widget class="QLabel" name="thumbnail">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>171</width>
<height>128</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="3" colspan="2">
<widget class="QLabel" name="appTitleLabel">
<item row="6" column="3" colspan="2">
<widget class="QLabel" name="stateDescription">
<property name="font">
<font>
<pointsize>48</pointsize>
<weight>75</weight>
<bold>true</bold>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>FlightGear 2017.1.0</string>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;©2017 FlightGear contributors. Licensed under the GNU Public License. See &lt;a href=&quot;http://www.flightgear.org&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;here&lt;/span&gt;&lt;/a&gt; for more information&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="2">
<widget class="QLabel" name="logoIcon">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources.qrc">:/app-icon-large</pixmap>
<string>TextLabel</string>
</property>
</widget>
</item>

View file

@ -106,6 +106,17 @@ LauncherMainWindow::LauncherMainWindow() :
m_serversModel = new MPServersModel(this);
m_serversModel->refresh();
// keep the description QLabel in sync as the current item changes
connect(m_ui->stateCombo,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this](int)
{
auto v = m_ui->stateCombo->currentData(QmlAircraftInfo::StateDescriptionRole);
m_ui->stateDescription->setText(v.toString());
m_ui->stateDescription->setVisible(!v.toString().isEmpty());
});
m_selectedAircraftInfo = new QmlAircraftInfo(this);
initQML();
m_subsystemIdleTimer = new QTimer(this);
@ -457,6 +468,27 @@ void LauncherMainWindow::onRun()
m_recentAircraft.pop_back();
}
if (m_ui->stateCombo->isVisible()) {
// apply state setting
std::string tag = m_ui->stateCombo->currentData(QmlAircraftInfo::StateTagRole).
toString().toStdString();
// implicit auto behaviour disabled for 2018.1, since it
// needs a bit more work
#if 0
if (tag == "auto") {
bool isExplictAuto = m_ui->stateCombo->currentData(QmlAircraftInfo::StateExplicitRole).toBool();
if (!isExplictAuto) {
tag = selectStateAutomatically();
qInfo() << "automatic state selection: picked:" << QString::fromStdString(tag);
}
}
#endif
if (!tag.empty() && (tag != "__default__")) {
m_config->setArg("state", tag);
}
} // of applying state selection
// aircraft paths
QSettings settings;
updateLocationHistory();
@ -490,6 +522,20 @@ void LauncherMainWindow::onRun()
qApp->exit(1);
}
std::string LauncherMainWindow::selectStateAutomatically()
{
if (m_ui->location->isAirborneLocation()) {
return "approach";
}
if (m_ui->location->isParkedLocation()) {
return "parked";
} else {
return "take-off";
}
return {}; // failed to compute, give up
}
void LauncherMainWindow::onApply()
{
@ -621,6 +667,7 @@ void LauncherMainWindow::maybeUpdateSelectedAircraft(QModelIndex index)
void LauncherMainWindow::updateSelectedAircraft()
{
m_selectedAircraftInfo->setUri(m_selectedAircraft);
QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
if (index.isValid()) {
QPixmap pm = index.data(Qt::DecorationRole).value<QPixmap>();
@ -648,10 +695,24 @@ void LauncherMainWindow::updateSelectedAircraft()
}
m_ui->location->setAircraftType(aircraftType);
const bool hasStates = m_selectedAircraftInfo->hasStates();
m_ui->stateCombo->setVisible(hasStates);
m_ui->stateLabel->setVisible(hasStates);
m_ui->stateDescription->setVisible(false);
if (hasStates) {
m_ui->stateCombo->setModel(m_selectedAircraftInfo->statesModel());
m_ui->stateDescription->setText(m_ui->stateCombo->currentData(QmlAircraftInfo::StateDescriptionRole).toString());
// hiden when no description is present
m_ui->stateDescription->setVisible(!m_ui->stateDescription->text().isEmpty());
}
} else {
m_ui->thumbnail->setPixmap(QPixmap());
m_ui->aircraftName->setText("");
m_ui->aircraftDescription->hide();
m_ui->stateCombo->hide();
m_ui->stateLabel->hide();
m_ui->stateDescription->hide();
m_ui->flyButton->setEnabled(false);
}
}
@ -812,6 +873,11 @@ QPointF LauncherMainWindow::mapToGlobal(QQuickItem *item, const QPointF &pos) co
return m_ui->aircraftList->mapToGlobal(scenePos.toPoint());
}
QmlAircraftInfo *LauncherMainWindow::selectedAircraftInfo() const
{
return m_selectedAircraftInfo;
}
void LauncherMainWindow::setSelectedAircraft(QUrl selectedAircraft)
{
if (m_selectedAircraft == selectedAircraft)

View file

@ -47,6 +47,7 @@ class ExtraSettingsSection;
class ViewCommandLinePage;
class MPServersModel;
class QQuickItem;
class QmlAircraftInfo;
class LauncherMainWindow : public QMainWindow
{
@ -61,6 +62,8 @@ class LauncherMainWindow : public QMainWindow
Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT)
Q_PROPERTY(QUrl selectedAircraft READ selectedAircraft WRITE setSelectedAircraft NOTIFY selectedAircraftChanged)
Q_PROPERTY(QmlAircraftInfo* selectedAircraftInfo READ selectedAircraftInfo NOTIFY selectedAircraftChanged)
public:
LauncherMainWindow();
virtual ~LauncherMainWindow();
@ -89,6 +92,8 @@ public:
// work around the fact, that this is not available on QQuickItem until 5.7
Q_INVOKABLE QPointF mapToGlobal(QQuickItem* item, const QPointF& pos) const;
QmlAircraftInfo* selectedAircraftInfo() const;
public slots:
void setSelectedAircraft(QUrl selectedAircraft);
@ -156,6 +161,8 @@ private:
void collectAircraftArgs();
void initQML();
std::string selectStateAutomatically();
QScopedPointer<Ui::Launcher> m_ui;
AircraftProxyModel* m_installedAircraftModel;
AircraftItemModel* m_aircraftModel;
@ -175,6 +182,7 @@ private:
LaunchConfig* m_config = nullptr;
ExtraSettingsSection* m_extraSettings = nullptr;
ViewCommandLinePage* m_viewCommandLinePage = nullptr;
QmlAircraftInfo* m_selectedAircraftInfo = nullptr;
};
#endif // of LAUNCHER_MAIN_WINDOW_HXX

View file

@ -441,6 +441,45 @@ void LocationWidget::restoreSettings()
onShowHistory();
}
bool LocationWidget::isParkedLocation() const
{
if (FGPositioned::isAirportType(m_location.ptr())) {
if (m_ui->parkingRadio->isChecked()) {
return true;
}
}
// treat all other ground starts as taxi or on runway, i.e engines
// running if possible
return false;
}
bool LocationWidget::isAirborneLocation() const
{
const int altitude = m_ui->altitudeSpinbox->value();
const bool altIsPositive = (altitude > 0);
const bool offsetSet = m_ui->offsetGroup->isChecked();
if (m_locationIsLatLon) {
return altIsPositive;
}
if (FGPositioned::isAirportType(m_location.ptr())) {
if (m_ui->runwayRadio->isChecked() && offsetSet) {
// in this case no altitude migth be set, but we assume
// it's still an airborne pos
return true;
}
// this allows for people using offsets from a parking position or
// similar weirdness :)
return altIsPositive;
}
// relative to a navaid or fix - base off altitude.
return altIsPositive;
}
void LocationWidget::restoreLocation(QVariantMap l)
{
if (l.contains("location-lat")) {

View file

@ -61,6 +61,12 @@ public:
QVariantMap saveLocation() const;
void restoreSettings();
/// used to automatically select aircraft state
bool isParkedLocation() const;
/// used to automatically select aircraft state
bool isAirborneLocation() const;
Q_SIGNALS:
void descriptionChanged(QString t);

View file

@ -6,6 +6,7 @@
#include <simgear/package/Install.hxx>
#include <simgear/package/Root.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/props/props_io.hxx>
#include <Include/version.h>
#include <Main/globals.hxx>
@ -14,6 +15,10 @@
using namespace simgear::pkg;
const int QmlAircraftInfo::StateTagRole = Qt::UserRole + 1;
const int QmlAircraftInfo::StateDescriptionRole = Qt::UserRole + 2;
const int QmlAircraftInfo::StateExplicitRole = Qt::UserRole + 3;
class QmlAircraftInfo::Delegate : public simgear::pkg::Delegate
{
public:
@ -51,6 +56,7 @@ protected:
{
Q_UNUSED(aReason);
if (aInstall->package() == p->packageRef()) {
p->checkForStates();
p->infoChanged();
}
}
@ -75,6 +81,145 @@ private:
QmlAircraftInfo* p;
};
////////////////////////////////////////////////////////////////////////////
struct StateInfo
{
std::string tag; // internal XML name
QString name; // human-readable name, or blank if we auto-generate this
QString description; // human-readable description
};
using AircraftStateVec = std::vector<StateInfo>;
static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
{
SGPropertyNode_ptr root(new SGPropertyNode);
try {
readProperties(setXMLPath, root);
} catch (sg_exception&) {
// malformed include or XML, just bail
return {};
}
if (!root->getNode("sim/state")) {
return {};
}
auto nodes = root->getNode("sim")->getChildren("state");
AircraftStateVec result;
result.reserve(nodes.size());
for (auto cn : nodes) {
result.push_back({cn->getStringValue("name"),
QString::fromStdString(cn->getStringValue("readable-name")),
QString::fromStdString(cn->getStringValue("description"))
});
qInfo() << QString::fromStdString(result.back().tag) << result.back().description;
}
return result;
}
QString humanNameFromStateTag(const std::string& tag)
{
if (tag == "approach") return QObject::tr("On approach");
if (tag == "take-off") return QObject::tr("Ready for Take-off");
if ((tag == "parking") || (tag == "cold-and-dark"))
return QObject::tr("Parked, cold & dark");
if (tag == "auto")
return QObject::tr("Automatic");
if (tag == "cruise")
return QObject::tr("Cruise");
qWarning() << Q_FUNC_INFO << "add for" << QString::fromStdString(tag);
// no mapping, let's use the tag directly
return QString::fromStdString(tag);
}
class StatesModel : public QAbstractListModel
{
Q_OBJECT
public:
StatesModel(const AircraftStateVec& states) :
_data(states)
{
// sort which places 'auto' item at the front if it exists
std::sort(_data.begin(), _data.end(), [](const StateInfo& a, const StateInfo& b) {
if (a.tag == "auto") return true;
if (b.tag == "auto") return false;
return a.tag < b.tag;
});
if (_data.front().tag == "auto") {
// track if the aircraft supplied an 'auto' state, in which case
// we will not run our own selection logic
_explicitAutoState = true;
} else {
// disabling this code for 2018.1, since it needs more testing
#if 0
_data.insert(_data.begin(), {"auto", {}, tr("Select state based on startup position.")});
#else
_data.insert(_data.begin(), {"__default__", tr("Parked"), tr("Default state for the aircraft (usually cold and dark)")});
#endif
}
}
int indexForTag(const std::string &tag) const
{
auto it = std::find_if(_data.begin(), _data.end(), [tag](const StateInfo& i) {
return i.tag == tag;
});
if (it == _data.end())
return -1;
return std::distance(_data.begin(), it);
}
int rowCount(const QModelIndex &parent) const override
{
return _data.size();
}
QVariant data(const QModelIndex &index, int role) const override
{
const StateInfo& s = _data.at(index.row());
// qInfo() << index.row() << s.name << QString::fromStdString(s.tag);
if (role == Qt::DisplayRole) {
if (s.name.isEmpty()) {
return humanNameFromStateTag(s.tag);
}
return s.name;
} else if (role == QmlAircraftInfo::StateTagRole) {
return QString::fromStdString(s.tag);
} else if (role == QmlAircraftInfo::StateDescriptionRole) {
return s.description;
} else if (role == QmlAircraftInfo::StateExplicitRole) {
if (s.tag == "auto")
return _explicitAutoState;
return true;
}
return {};
}
QHash<int, QByteArray> roleNames() const override
{
auto result = QAbstractListModel::roleNames();
result[Qt::DisplayRole] = "name";
result[QmlAircraftInfo::StateTagRole] = "tag";
result[QmlAircraftInfo::StateDescriptionRole] = "description";
return result;
}
private:
AircraftStateVec _data;
bool _explicitAutoState = false;
};
////////////////////////////////////////////////////////////////////////////
QmlAircraftInfo::QmlAircraftInfo(QObject *parent)
: QObject(parent)
, _delegate(new Delegate(this))
@ -297,6 +442,26 @@ AircraftItemPtr QmlAircraftInfo::resolveItem() const
return _item;
}
void QmlAircraftInfo::checkForStates()
{
QString path = pathOnDisk();
if (path.isEmpty()) {
_statesModel.reset();
emit infoChanged();
return;
}
auto states = readAircraftStates(SGPath::fromUtf8(path.toUtf8().toStdString()));
if (states.empty()) {
_statesModel.reset();
emit infoChanged();
return;
}
_statesModel.reset(new StatesModel(states));
emit infoChanged();
}
void QmlAircraftInfo::setUri(QUrl u)
{
if (uri() == u)
@ -304,9 +469,16 @@ void QmlAircraftInfo::setUri(QUrl u)
_item.clear();
_package.clear();
_statesModel.reset();
if (u.isLocalFile()) {
_item = LocalAircraftCache::instance()->findItemWithUri(u);
if (!_item) {
// scan still active or aircraft not found, let's bail out
// and rely on caller to try again
return;
}
int vindex = _item->indexOfVariant(u);
// we need to offset the variant index to allow for the different
// indexing schemes here (primary included) and in the cache (primary
@ -322,6 +494,8 @@ void QmlAircraftInfo::setUri(QUrl u)
}
}
checkForStates();
emit uriChanged();
emit infoChanged();
emit downloadChanged();
@ -341,6 +515,8 @@ void QmlAircraftInfo::setVariant(int variant)
return;
_variant = variant;
checkForStates();
emit infoChanged();
emit variantChanged(_variant);
}
@ -412,7 +588,7 @@ PackageRef QmlAircraftInfo::packageRef() const
void QmlAircraftInfo::setDownloadBytes(int bytes)
{
_downloadBytes = bytes;
emit downloadChanged();;
emit downloadChanged();
}
QStringList QmlAircraftInfo::variantNames() const
@ -441,3 +617,14 @@ bool QmlAircraftInfo::isPackaged() const
{
return _package != PackageRef();
}
QAbstractListModel *QmlAircraftInfo::statesModel()
{
if (!hasStates())
return nullptr;
return _statesModel.data();
}
#include "QmlAircraftInfo.moc"

View file

@ -6,6 +6,7 @@
#include <QObject>
#include <QUrl>
#include <QSharedPointer>
#include <QAbstractListModel>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.hxx>
@ -54,6 +55,10 @@ class QmlAircraftInfo : public QObject
Q_PROPERTY(bool isPackaged READ isPackaged NOTIFY infoChanged)
Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged)
Q_PROPERTY(QAbstractListModel* statesModel READ statesModel NOTIFY infoChanged)
public:
explicit QmlAircraftInfo(QObject *parent = nullptr);
virtual ~QmlAircraftInfo();
@ -95,6 +100,17 @@ public:
QStringList variantNames() const;
bool isPackaged() const;
bool hasStates() const
{
return !_statesModel.isNull();
}
static const int StateTagRole;
static const int StateDescriptionRole;
static const int StateExplicitRole;
QAbstractListModel* statesModel();
signals:
void uriChanged();
void infoChanged();
@ -108,6 +124,9 @@ public slots:
void setVariant(int variant);
private:
AircraftItemPtr resolveItem() const;
void checkForStates();
class Delegate;
std::unique_ptr<Delegate> _delegate;
@ -115,9 +134,7 @@ private:
AircraftItemPtr _item;
int _variant = 0;
int _downloadBytes = 0;
AircraftItemPtr resolveItem() const;
int m_variant;
QScopedPointer<QAbstractListModel> _statesModel;
};
#endif // QMLAIRCRAFTINFO_HXX