1
0
Fork 0

Launcher can save/load configs to disk

Aircraft state is also persisted to configs and on flying
This commit is contained in:
James Turner 2018-06-27 23:06:39 +01:00
parent c013eb74ab
commit 5ebee55632
15 changed files with 290 additions and 103 deletions

View file

@ -7,12 +7,52 @@
#include <QSettings>
#include <QDebug>
#include <QIODevice>
#include <QDataStream>
static bool static_enableDownloadDirUI = true;
static QSettings::Format static_binaryFormat = QSettings::InvalidFormat;
static bool binaryReadFunc(QIODevice &device, QSettings::SettingsMap &map)
{
QDataStream ds(&device);
int count;
ds >> count;
for (int i=0; i < count; ++i) {
QString k;
QVariant v;
ds >> k >> v;
map.insert(k, v);
}
return true;
}
static bool binaryWriteFunc(QIODevice &device, const QSettings::SettingsMap &map)
{
QDataStream ds(&device);
ds << map.size();
Q_FOREACH(QString key, map.keys()) {
ds << key << map.value(key);
}
return true;
}
LaunchConfig::LaunchConfig(QObject* parent) :
QObject(parent)
{
if (static_binaryFormat == QSettings::InvalidFormat) {
static_binaryFormat = QSettings::registerFormat("fglaunch",
&binaryReadFunc,
&binaryWriteFunc);
}
}
LaunchConfig::~LaunchConfig()
{
}
void LaunchConfig::reset()
@ -130,11 +170,59 @@ QString LaunchConfig::htmlForCommandLine()
return html;
}
bool LaunchConfig::saveConfigToINI()
{
// create settings using default type (INI) and path (inside FG_HOME),
// as setup in initQSettings()
m_loadSaveSettings.reset(new QSettings);
emit save();
m_loadSaveSettings->sync();
m_loadSaveSettings.reset();
return true;
}
bool LaunchConfig::loadConfigFromINI()
{
// create settings using default type (INI) and path (inside FG_HOME),
// as setup in initQSettings()
m_loadSaveSettings.reset(new QSettings);
emit restore();
emit postRestore();
m_loadSaveSettings.reset();
return true;
}
bool LaunchConfig::saveConfigToFile(QString path)
{
m_loadSaveSettings.reset(new QSettings(path, static_binaryFormat));
emit save();
m_loadSaveSettings.reset();
return true;
}
bool LaunchConfig::loadConfigFromFile(QString path)
{
m_loadSaveSettings.reset(new QSettings(path, static_binaryFormat));
emit restore();
// some things have an ordering dependency, give them a chance to run
// after other settings have been restored (eg, location or aircraft)
emit postRestore();
m_loadSaveSettings.reset();
return true;
}
QVariant LaunchConfig::getValueForKey(QString group, QString key, QVariant defaultValue) const
{
QSettings settings;
settings.beginGroup(group);
auto v = settings.value(key, defaultValue);
if (!m_loadSaveSettings) {
// becuase we load settings on component completion, we need
// to create the default implementation (using the INI file)
// on demand
m_loadSaveSettings.reset(new QSettings);
}
m_loadSaveSettings->beginGroup(group);
auto v = m_loadSaveSettings->value(key, defaultValue);
bool convertedOk = v.convert(defaultValue.type());
if (!convertedOk) {
qWarning() << "type forcing on loaded value failed:" << key << v << v.typeName() << defaultValue;
@ -145,11 +233,11 @@ QVariant LaunchConfig::getValueForKey(QString group, QString key, QVariant defau
void LaunchConfig::setValueForKey(QString group, QString key, QVariant var)
{
QSettings settings;
settings.beginGroup(group);
Q_ASSERT(m_loadSaveSettings);
m_loadSaveSettings->beginGroup(group);
// qInfo() << "saving" << key << "with value" << var << var.typeName();
settings.setValue(key, var);
settings.endGroup();
m_loadSaveSettings->setValue(key, var);
m_loadSaveSettings->endGroup();
}
QString LaunchConfig::defaultDownloadDir() const

View file

@ -4,6 +4,10 @@
#include <set>
#include <QObject>
#include <QVariant>
#include <QScopedPointer>
// forwards decls
class QSettings;
namespace flightgear { class Options; }
@ -34,6 +38,7 @@ public:
LaunchConfig(QObject* parent = nullptr);
~LaunchConfig();
void reset();
void applyToOptions() const;
@ -52,10 +57,12 @@ public:
Q_INVOKABLE QString htmlForCommandLine();
bool saveConfigToINI();
bool loadConfigFromINI();
// ensure a property is /not/ set?
Q_INVOKABLE bool saveConfigToFile(QString path);
// save and restore API?
Q_INVOKABLE bool loadConfigFromFile(QString path);
Q_INVOKABLE QVariant getValueForKey(QString group, QString key, QVariant defaultValue = QVariant()) const;
Q_INVOKABLE void setValueForKey(QString group, QString key, QVariant var);
@ -72,11 +79,17 @@ public:
signals:
void collect();
void save();
void restore();
void postRestore();
private:
std::set<std::string> extraArgNames() const;
std::vector<Arg> m_values;
QString m_defaultDownloadDir;
mutable QScopedPointer<QSettings> m_loadSaveSettings;
};
#endif

View file

@ -11,6 +11,7 @@
#include <QQuickWindow>
#include <QQmlComponent>
#include <QPushButton>
#include <QFileDialog>
// simgear headers
#include <simgear/package/Install.hxx>
@ -61,6 +62,8 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
m_config = new LaunchConfig(this);
connect(m_config, &LaunchConfig::collect, this, &LauncherController::collectAircraftArgs);
connect(m_config, &LaunchConfig::save, this, &LauncherController::saveAircraft);
connect(m_config, &LaunchConfig::restore, this, &LauncherController::restoreAircraft);
m_location->setLaunchConfig(m_config);
connect(m_location, &LocationController::descriptionChanged,
@ -165,7 +168,7 @@ bool LauncherController::inAppResult() const
return m_appModeResult;
}
void LauncherController::restoreSettings()
void LauncherController::initialRestoreSettings()
{
m_selectedAircraft = m_aircraftHistory->mostRecent();
if (m_selectedAircraft.isEmpty()) {
@ -179,8 +182,7 @@ void LauncherController::restoreSettings()
}
}
m_location->restoreSettings();
m_location->restoreSearchHistory();
QVariantMap currentLocation = m_locationHistory->mostRecent();
if (currentLocation.isEmpty()) {
// use the default
@ -193,19 +195,22 @@ void LauncherController::restoreSettings()
}
m_location->restoreLocation(currentLocation);
emit selectedAircraftChanged(m_selectedAircraft);
updateSelectedAircraft();
m_serversModel->requestRestore();
m_aircraftState = m_config->getValueForKey("", "selected-aircraft-state", QString()).toString();
emit selectedAircraftStateChanged();
emit summaryChanged();
}
void LauncherController::saveSettings()
{
emit requestSaveState();
QSettings settings;
settings.setValue("window-geometry", m_window->geometry());
m_config->saveConfigToINI();
m_aircraftHistory->saveToSettings();
m_locationHistory->saveToSettings();
}
@ -230,6 +235,18 @@ void LauncherController::collectAircraftArgs()
}
}
if (m_selectedAircraftInfo->hasStates() && !m_aircraftState.isEmpty()) {
QString state = m_aircraftState;
if ((m_aircraftState == "auto") && !m_selectedAircraftInfo->haveExplicitAutoState()) {
state = selectAircraftStateAutomatically();
qInfo() << "doing launcher auto state selection, picked:" + state;
}
if (!state.isEmpty()) {
m_config->setArg("state", state);
}
}
// scenery paths
QSettings settings;
Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) {
@ -242,6 +259,23 @@ void LauncherController::collectAircraftArgs()
}
}
void LauncherController::saveAircraft()
{
m_config->setValueForKey("", "selected-aircraft", m_selectedAircraft);
if (!m_aircraftState.isEmpty()) {
m_config->setValueForKey("", "selected-aircraft-state", m_aircraftState);
}
}
void LauncherController::restoreAircraft()
{
m_selectedAircraft = m_config->getValueForKey("", "selected-aircraft", QUrl()).toUrl();
m_aircraftState = m_config->getValueForKey("", "selected-aircraft-state", QString()).toString();
emit selectedAircraftChanged(m_selectedAircraft);
updateSelectedAircraft();
emit selectedAircraftStateChanged();
}
void LauncherController::doRun()
{
flightgear::Options* opt = flightgear::Options::sharedInstance();
@ -318,8 +352,13 @@ QString LauncherController::selectAircraftStateAutomatically()
if (!m_selectedAircraftInfo)
return {};
if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach"))
{
if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("cruise")) {
if (m_location->altitudeFt() > 6000) {
return "cruise";
}
}
if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach")) {
return "approach";
}
@ -452,8 +491,11 @@ void LauncherController::setSelectedAircraft(QUrl selectedAircraft)
return;
m_selectedAircraft = selectedAircraft;
m_aircraftState.clear();
updateSelectedAircraft();
emit selectedAircraftChanged(m_selectedAircraft);
emit selectedAircraftStateChanged();
}
void LauncherController::setSettingsSearchTerm(QString settingsSearchTerm)
@ -708,5 +750,24 @@ void LauncherController::requestChangeDataPath()
settings.setValue("fg-root", "!ask");
} // scope the ensure settings are written nicely
flightgear::restartTheApp();
flightgear::restartTheApp();
}
void LauncherController::openConfig()
{
QString file = QFileDialog::getOpenFileName(nullptr, tr("Choose a saved configuration"),
{}, "*.fglaunch");
if (file.isEmpty())
return;
m_config->loadConfigFromFile(file);
}
void LauncherController::saveConfigAs()
{
QString file = QFileDialog::getSaveFileName(nullptr, tr("Save the current configuration"),
{}, "*.fglaunch");
if (file.isEmpty())
return;
m_config->saveConfigToFile(file);
}

View file

@ -59,6 +59,8 @@ class LauncherController : public QObject
Q_PROPERTY(QmlAircraftInfo* selectedAircraftInfo READ selectedAircraftInfo NOTIFY selectedAircraftChanged)
Q_PROPERTY(QString selectedAircraftState MEMBER m_aircraftState NOTIFY selectedAircraftStateChanged)
Q_PROPERTY(bool isSearchActive READ isSearchActive NOTIFY searchChanged)
Q_PROPERTY(QString settingsSearchTerm READ settingsSearchTerm WRITE setSettingsSearchTerm NOTIFY searchChanged)
@ -131,7 +133,6 @@ public:
Q_INVOKABLE QVariantList defaultSplashUrls() const;
Q_INVOKABLE QString selectAircraftStateAutomatically();
LaunchConfig* config() const
{ return m_config; }
@ -144,7 +145,7 @@ public:
AircraftItemModel* baseAircraftModel() const
{ return m_aircraftModel; }
void restoreSettings();
void initialRestoreSettings();
void saveSettings();
LocationController* location() const
@ -169,18 +170,13 @@ public:
signals:
void selectedAircraftChanged(QUrl selectedAircraft);
void selectedAircraftStateChanged();
void searchChanged();
void summaryChanged();
void canFlyChanged();
/**
* @brief requestSaveState - signal to request QML settings to save their
* state to persistent storage
*/
void requestSaveState();
void viewCommandLine();
public slots:
@ -198,11 +194,16 @@ public slots:
void requestRestoreDefaults();
void requestChangeDataPath();
void openConfig();
void saveConfigAs();
private slots:
void onAircraftInstalledCompleted(QModelIndex index);
void onAircraftInstallFailed(QModelIndex index, QString errorMessage);
void saveAircraft();
void restoreAircraft();
private:
/**
* Check if the passed index is the selected aircraft, and if so, refresh
@ -220,6 +221,8 @@ private:
void collectAircraftArgs();
QString selectAircraftStateAutomatically();
private:
QWindow* m_window = nullptr;
@ -231,6 +234,7 @@ private:
LocationController* m_location = nullptr;
QUrl m_selectedAircraft;
QString m_aircraftState;
AircraftType m_aircraftType = Airplane;
int m_ratingFilters[4] = {3, 3, 3, 3};
LaunchConfig* m_config = nullptr;

View file

@ -39,6 +39,15 @@ LauncherMainWindow::LauncherMainWindow() :
#if defined(Q_OS_MAC)
QMenuBar* mb = new QMenuBar();
QMenu* fileMenu = mb->addMenu(tr("File"));
QAction* openAction = fileMenu->addAction(tr("Open saved configuration..."));
connect(openAction, &QAction::triggered,
m_controller, &LauncherController::openConfig);
QAction* saveAction = fileMenu->addAction(tr("Save configuration as..."));
connect(saveAction, &QAction::triggered,
m_controller, &LauncherController::saveConfigAs);
QMenu* toolsMenu = mb->addMenu(tr("Tools"));
QAction* restoreDefaultsAction = toolsMenu->addAction(tr("Restore defaults..."));
connect(restoreDefaultsAction, &QAction::triggered,
@ -57,7 +66,7 @@ LauncherMainWindow::LauncherMainWindow() :
qa->setShortcut(QKeySequence("Ctrl+Q"));
connect(qa, &QAction::triggered, m_controller, &LauncherController::quit);
m_controller->restoreSettings();
m_controller->initialRestoreSettings();
flightgear::launcherSetSceneryPaths();
auto addOnsCtl = new AddOnsController(this);

View file

@ -391,14 +391,32 @@ void LocationController::setLaunchConfig(LaunchConfig *config)
{
m_config = config;
connect(m_config, &LaunchConfig::collect, this, &LocationController::onCollectConfig);
connect(m_config, &LaunchConfig::save, this, &LocationController::onSaveCurrentLocation);
connect(m_config, &LaunchConfig::restore, this, &LocationController::onRestoreCurrentLocation);
}
void LocationController::restoreSettings()
void LocationController::restoreSearchHistory()
{
QSettings settings;
m_recentLocations = loadPositionedList(settings.value("recent-locations"));
}
void LocationController::onRestoreCurrentLocation()
{
QVariantMap vm = m_config->getValueForKey("", "current-location", QVariantMap()).toMap();
if (vm.empty())
return;
restoreLocation(vm);
}
void LocationController::onSaveCurrentLocation()
{
m_config->setValueForKey("", "current-location", saveLocation());
}
bool LocationController::isParkedLocation() const
{
if (m_airportLocation) {
@ -417,24 +435,25 @@ bool LocationController::isAirborneLocation() const
const bool altIsPositive = (m_altitudeFt > 0);
if (m_locationIsLatLon) {
return altIsPositive;
return (m_altitudeType != AltitudeType::Off) && altIsPositive;
}
if (m_airportLocation) {
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
if (onRunway && m_offsetEnabled) {
const bool onRunway =
(m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)) ||
m_useActiveRunway;
if (onRunway && m_onFinal) {
// in this case no altitude might be set, but we assume
// it's still an airborne pos
// it's still an airborne position
return true;
}
// this allows for people using offsets from a parking position or
// similar weirdness :)
return altIsPositive;
return false;
}
// relative to a navaid or fix - base off altitude.
return altIsPositive;
return (m_altitudeType != AltitudeType::Off) && altIsPositive;
}
int LocationController::offsetRadial() const
@ -709,7 +728,7 @@ void LocationController::restoreLocation(QVariantMap l)
if (l.contains("location-apt-runway")) {
QString runway = l.value("location-apt-runway").toString().toUpper();
if (runway == "ACTIVE") {
if (runway == QStringLiteral("ACTIVE")) {
m_useActiveRunway = true;
} else {
m_detailLocation = m_airportLocation->getRunwayByIdent(runway.toStdString());

View file

@ -101,7 +101,7 @@ public:
void restoreLocation(QVariantMap l);
QVariantMap saveLocation() const;
void restoreSettings();
void restoreSearchHistory();
/// used to automatically select aircraft state
bool isParkedLocation() const;
@ -170,6 +170,10 @@ public:
return m_locationIsLatLon;
}
int altitudeFt() const
{
return m_altitudeFt;
}
public slots:
void setOffsetRadial(int offsetRadial);
@ -192,6 +196,8 @@ Q_SIGNALS:
private Q_SLOTS:
void onCollectConfig();
void onRestoreCurrentLocation();
void onSaveCurrentLocation();
private:
void onSearchComplete();

View file

@ -155,7 +155,6 @@ class StatesModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool hasExplicitAuto READ hasExplicitAuto CONSTANT)
public:
StatesModel()
{
@ -181,11 +180,15 @@ public:
// we will not run our own selection logic
_explicitAutoState = true;
} else {
// disabling this code for 2018.1, since it needs more testing
_data.insert(_data.begin(), {{"auto"}, {}, tr("Select state based on startup position.")});
}
}
Q_INVOKABLE int indexForTag(QString s) const
{
return indexForTag(s.toStdString());
}
int indexForTag(const std::string &tag) const
{
auto it = std::find_if(_data.begin(), _data.end(), [tag](const StateInfo& i) {
@ -684,6 +687,11 @@ bool QmlAircraftInfo::hasState(QString name) const
return _statesModel->hasState(name);
}
bool QmlAircraftInfo::haveExplicitAutoState() const
{
return _statesModel->hasExplicitAuto();
}
StatesModel *QmlAircraftInfo::statesModel()
{
return _statesModel.data();

View file

@ -107,6 +107,8 @@ public:
bool hasState(QString name) const;
bool haveExplicitAutoState() const;
static const int StateTagRole;
static const int StateDescriptionRole;
static const int StateExplicitRole;

View file

@ -9,14 +9,6 @@ Item {
value: timeOfDay.summary().concat(weatherSettings.summary());
}
Connections {
target: _launcher
onRequestSaveState: {
timeOfDaySettings.saveState();
weatherSettings.saveState();
}
}
Flickable {
contentHeight: sectionColumn.childrenRect.height
flickableDirection: Flickable.VerticalFlick

View file

@ -131,8 +131,8 @@ Item {
z: 100
items: [
MenuItem { text:qsTr("Open saved configuration..."); enabled: false },
MenuItem { text:qsTr("Save configuration..."); enabled: false },
MenuItem { text:qsTr("Open saved configuration..."); onTriggered: _launcher.openConfig(); },
MenuItem { text:qsTr("Save configuration as..."); onTriggered: _launcher.saveConfigAs(); },
MenuDivider {},
MenuItem { text:qsTr("View command line"); onTriggered: _launcher.viewCommandLine(); },
MenuItem { text:qsTr("Select data files location..."); onTriggered: _launcher.requestChangeDataPath(); },

View file

@ -13,8 +13,6 @@ Item {
property bool enabled: true
property int currentIndex: 0
property bool __dummy: false
property alias header: choicesHeader.sourceComponent
property string headerText: ""
implicitHeight: Math.max(label.implicitHeight, currentChoiceFrame.height)
@ -166,20 +164,11 @@ Item {
width: menuWidth
// optional header component:
Loader {
id: choicesHeader
active: root.haveHeader()
// default component is just a plain text element, same as
// normal items
sourceComponent: StyledText {
text: root.headerText
height: implicitHeight + Style.margin
width: choicesColumn.width
}
height: item ? item.height : 0
width: item ? item.width : 0
StyledText {
text: root.headerText
visible: root.haveHeader();
height: implicitHeight + Style.margin
width: choicesColumn.width
// essentially the same mouse area as normal items
MouseArea {
@ -191,7 +180,7 @@ Item {
root.select(-1);
}
}
} // of header loader
}
function calculateMenuWidth()
{

View file

@ -43,6 +43,12 @@ Item {
onCollect: apply();
}
Connections {
target: _config
onRestore: root.restoreState();
onSave: root.saveState();
}
Rectangle {
// this is the 'search hit highlight effect'
anchors.fill: parent

View file

@ -20,26 +20,8 @@ Item {
renderSection.summary());
}
Connections {
target: _launcher
onRequestSaveState: settings.saveState();
}
Component.onDestruction: {
settings.saveState();
}
function saveState()
Flickable
{
mpSettings.saveState();
downloadSettings.saveState();
generalSettings.saveState();
renderSection.saveState();
extraArgsSection.saveState();
windowSettings.saveState();
}
Flickable {
id: settingsFlick
contentHeight: sectionColumn.childrenRect.height
flickableDirection: Flickable.VerticalFlick

View file

@ -86,7 +86,7 @@ Item {
// dynamic text sizing, so bind it manually
y: logoText.y + Style.margin + logoText.contentHeight
wrapMode: Text.WordWrap
text: "Licenced under the GNU Public License (GPL)- click for more info"
text: "Licenced under the GNU Public License (GPL) - click for more info"
baseTextColor: "white"
style: Text.Outline
styleColor: "black"
@ -216,12 +216,29 @@ Item {
width: summaryGrid.middleColumnWidth
spacing: Style.margin
Component.onCompleted: updateComboFromController();
function updateComboFromController()
{
stateSelectionCombo.currentIndex = _launcher.selectedAircraftInfo.statesModel.indexForTag(_launcher.selectedAircraftState)
}
PopupChoice {
id: stateSelectionCombo
model: _launcher.selectedAircraftInfo.statesModel
displayRole: "name"
label: qsTr("State:")
width: parent.width
width: parent.width
headerText: qsTr("Default state")
function select(index)
{
if (index === -1) {
_launcher.selectedAircraftState = "";
} else {
_launcher.selectedAircraftState = model.tagForState(index);
}
}
}
StyledText {
@ -234,22 +251,13 @@ Item {
}
Connections {
target: _config
onCollect: {
if (!_launcher.selectedAircraftInfo.hasStates)
return;
target: _launcher.selectedAircraftInfo
onInfoChanged: stateSelectionGroup.updateComboFromController()
}
var state = _launcher.selectedAircraftInfo.statesModel.tagForState(stateSelectionCombo.currentIndex);
if (state === "auto" && !_launcher.selectedAircraftInfo.statesModel.hasExplicitAuto) {
// auto state selection if not handled by aircraft
state = _launcher.selectAircraftStateAutomatically();
console.info("launcher auto state selection, picked:" + state)
}
if (state !== "__default__") { // don't set arg in default case
_config.setArg("state", state);
}
}
Connections {
target: _launcher
onSelectedAircraftStateChanged: stateSelectionGroup.updateComboFromController()
} // of connections
}