1
0
Fork 0

Working on airport entry

This commit is contained in:
James Turner 2018-07-18 11:32:47 +01:00
parent 9098219032
commit 86786496a5
15 changed files with 640 additions and 311 deletions

View file

@ -154,6 +154,8 @@ if (HAVE_QT)
QmlRadioButtonHelper.hxx QmlRadioButtonHelper.hxx
UnitsModel.cxx UnitsModel.cxx
UnitsModel.hxx UnitsModel.hxx
NavaidSearchModel.hxx
NavaidSearchModel.cxx
) )
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON) set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)

View file

@ -48,6 +48,7 @@
#include "NavaidDiagram.hxx" #include "NavaidDiagram.hxx"
#include "QmlRadioButtonHelper.hxx" #include "QmlRadioButtonHelper.hxx"
#include "UnitsModel.hxx" #include "UnitsModel.hxx"
#include "NavaidSearchModel.hxx"
using namespace simgear::pkg; using namespace simgear::pkg;
@ -55,9 +56,6 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
QObject(parent), QObject(parent),
m_window(window) m_window(window)
{ {
qRegisterMetaType<QuantityValue>();
qRegisterMetaTypeStreamOperators<QuantityValue>("Quantity");
m_serversModel = new MPServersModel(this); m_serversModel = new MPServersModel(this);
m_location = new LocationController(this); m_location = new LocationController(this);
m_locationHistory = new RecentLocationsModel(this); m_locationHistory = new RecentLocationsModel(this);
@ -137,6 +135,8 @@ void LauncherController::initQML()
qmlRegisterUncreatableType<LaunchConfig>("FlightGear.Launcher", 1, 0, "LaunchConfig", "Singleton API"); qmlRegisterUncreatableType<LaunchConfig>("FlightGear.Launcher", 1, 0, "LaunchConfig", "Singleton API");
qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API"); qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch");
qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum"); qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel"); qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
@ -158,6 +158,8 @@ void LauncherController::initQML()
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram"); qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup"); qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
qmlRegisterSingletonType(QUrl("qrc:///qml/OverlayShared.qml"), "FlightGear", 1, 0, "OverlayShared");
QNetworkDiskCache* diskCache = new QNetworkDiskCache(this); QNetworkDiskCache* diskCache = new QNetworkDiskCache(this);
SGPath cachePath = globals->get_fg_home() / "PreviewsCache"; SGPath cachePath = globals->get_fg_home() / "PreviewsCache";
diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str())); diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str()));

View file

@ -34,6 +34,7 @@
#include "NavaidDiagram.hxx" #include "NavaidDiagram.hxx"
#include "LaunchConfig.hxx" #include "LaunchConfig.hxx"
#include "DefaultAircraftLocator.hxx" #include "DefaultAircraftLocator.hxx"
#include "NavaidSearchModel.hxx"
#include <Airports/airport.hxx> #include <Airports/airport.hxx>
#include <Airports/groundnetwork.hxx> #include <Airports/groundnetwork.hxx>
@ -49,81 +50,6 @@ using namespace flightgear;
const unsigned int MAX_RECENT_LOCATIONS = 64; const unsigned int MAX_RECENT_LOCATIONS = 64;
QString fixNavaidName(QString s)
{
// split into words
QStringList words = s.split(QChar(' '));
QStringList changedWords;
Q_FOREACH(QString w, words) {
QString up = w.toUpper();
// expand common abbreviations
// note these are not translated, since they are abbreivations
// for English-langauge airports, mostly in the US/Canada
if (up == "FLD") {
changedWords.append("Field");
continue;
}
if (up == "CO") {
changedWords.append("County");
continue;
}
if ((up == "MUNI") || (up == "MUN")) {
changedWords.append("Municipal");
continue;
}
if (up == "MEM") {
changedWords.append("Memorial");
continue;
}
if (up == "RGNL") {
changedWords.append("Regional");
continue;
}
if (up == "CTR") {
changedWords.append("Center");
continue;
}
if (up == "INTL") {
changedWords.append("International");
continue;
}
// occurs in many Australian airport names in our DB
if (up == "(NSW)") {
changedWords.append("(New South Wales)");
continue;
}
if ((up == "VOR") || (up == "NDB")
|| (up == "VOR-DME") || (up == "VORTAC")
|| (up == "NDB-DME")
|| (up == "AFB") || (up == "RAF"))
{
changedWords.append(w);
continue;
}
if ((up =="[X]") || (up == "[H]") || (up == "[S]")) {
continue; // consume
}
QChar firstChar = w.at(0).toUpper();
w = w.mid(1).toLower();
w.prepend(firstChar);
changedWords.append(w);
}
return changedWords.join(QChar(' '));
}
QVariant savePositionList(const FGPositionedList& posList) QVariant savePositionList(const FGPositionedList& posList)
{ {
QVariantList vl; QVariantList vl;
@ -164,214 +90,10 @@ FGPositionedList loadPositionedList(QVariant v)
return result; return result;
} }
class IdentSearchFilter : public FGPositioned::TypeFilter
{
public:
IdentSearchFilter(LauncherController::AircraftType aircraft)
{
addType(FGPositioned::VOR);
addType(FGPositioned::FIX);
addType(FGPositioned::NDB);
if (aircraft == LauncherController::Helicopter) {
addType(FGPositioned::HELIPAD);
}
if (aircraft == LauncherController::Seaplane) {
addType(FGPositioned::SEAPORT);
} else {
addType(FGPositioned::AIRPORT);
}
}
};
class NavSearchModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool isSearchActive READ isSearchActive NOTIFY searchActiveChanged)
Q_PROPERTY(bool haveExistingSearch READ haveExistingSearch NOTIFY haveExistingSearchChanged)
enum Roles {
GeodRole = Qt::UserRole + 1,
GuidRole = Qt::UserRole + 2,
IdentRole = Qt::UserRole + 3,
NameRole = Qt::UserRole + 4,
IconRole = Qt::UserRole + 5,
TypeRole = Qt::UserRole + 6,
NavFrequencyRole = Qt::UserRole + 7
};
public:
NavSearchModel() { }
enum AircraftType
{
Airplane = LauncherController::Airplane,
Seaplane = LauncherController::Seaplane,
Helicopter = LauncherController::Helicopter,
Airship = LauncherController::Airship
};
Q_ENUMS(AircraftType)
Q_INVOKABLE void setSearch(QString t, AircraftType aircraft)
{
beginResetModel();
m_items.clear();
m_ids.clear();
std::string term(t.toUpper().toStdString());
IdentSearchFilter filter(static_cast<LauncherController::AircraftType>(aircraft));
FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true);
m_ids.reserve(exactMatches.size());
m_items.reserve(exactMatches.size());
for (auto match : exactMatches) {
m_ids.push_back(match->guid());
m_items.push_back(match);
}
endResetModel();
m_search.reset(new NavDataCache::ThreadedGUISearch(term));
QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
m_searchActive = true;
emit searchActiveChanged();
emit haveExistingSearchChanged();
}
bool isSearchActive() const
{
return m_searchActive;
}
bool haveExistingSearch() const
{
return m_searchActive || (!m_items.empty());
}
int rowCount(const QModelIndex&) const override
{
return m_ids.size();
}
QVariant data(const QModelIndex& index, int role) const override
{
if (!index.isValid())
return QVariant();
FGPositionedRef pos = itemAtRow(index.row());
switch (role) {
case GuidRole: return static_cast<qlonglong>(pos->guid());
case IdentRole: return QString::fromStdString(pos->ident());
case NameRole:
return fixNavaidName(QString::fromStdString(pos->name()));
case NavFrequencyRole: {
FGNavRecord* nav = fgpositioned_cast<FGNavRecord>(pos);
return nav ? nav->get_freq() : 0;
}
case TypeRole: return static_cast<QmlPositioned::Type>(pos->type());
case IconRole:
return AirportDiagram::iconForPositioned(pos,
AirportDiagram::SmallIcons | AirportDiagram::LargeAirportPlans);
}
return {};
}
FGPositionedRef itemAtRow(unsigned int row) const
{
FGPositionedRef pos = m_items[row];
if (!pos.valid()) {
pos = NavDataCache::instance()->loadById(m_ids[row]);
m_items[row] = pos;
}
return pos;
}
void setItems(const FGPositionedList& items)
{
beginResetModel();
m_searchActive = false;
m_items = items;
m_ids.clear();
for (unsigned int i=0; i < items.size(); ++i) {
m_ids.push_back(m_items[i]->guid());
}
endResetModel();
emit searchActiveChanged();
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[GeodRole] = "geod";
result[GuidRole] = "guid";
result[IdentRole] = "ident";
result[NameRole] = "name";
result[IconRole] = "icon";
result[TypeRole] = "type";
result[NavFrequencyRole] = "frequency";
return result;
}
Q_SIGNALS:
void searchComplete();
void searchActiveChanged();
void haveExistingSearchChanged();
private slots:
void onSearchResultsPoll()
{
if (m_search.isNull()) {
return;
}
PositionedIDVec newIds = m_search->results();
if (!newIds.empty()) {
m_ids.reserve(newIds.size());
beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
for (auto id : newIds) {
m_ids.push_back(id);
m_items.push_back({}); // null ref
}
endInsertRows();
}
if (m_search->isComplete()) {
m_searchActive = false;
m_search.reset();
emit searchComplete();
emit searchActiveChanged();
emit haveExistingSearchChanged();
} else {
QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
}
}
private:
PositionedIDVec m_ids;
mutable FGPositionedList m_items;
bool m_searchActive = false;
QScopedPointer<NavDataCache::ThreadedGUISearch> m_search;
};
LocationController::LocationController(QObject *parent) : LocationController::LocationController(QObject *parent) :
QObject(parent) QObject(parent)
{ {
qmlRegisterUncreatableType<NavSearchModel>("FlightGear.Launcher", 1, 0, "NavSearchModel", "no"); m_searchModel = new NavaidSearchModel;
m_searchModel = new NavSearchModel;
m_detailQml = new QmlPositioned(this); m_detailQml = new QmlPositioned(this);
m_baseQml = new QmlPositioned(this); m_baseQml = new QmlPositioned(this);
@ -1014,6 +736,22 @@ void LocationController::applyAltitude()
} }
} }
void LocationController::applyOnFinal()
{
if (m_onFinal) {
if (!m_altitudeEnabled) {
m_config->setArg("glideslope", std::string("3.0"));
}
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
m_config->setArg("on-ground", std::string("false"));
applyAirspeed();
applyAltitude();
}
}
void LocationController::onCollectConfig() void LocationController::onCollectConfig()
{ {
if (m_skipFromArgs) { if (m_skipFromArgs) {
@ -1041,6 +779,7 @@ void LocationController::onCollectConfig()
if (m_useActiveRunway) { if (m_useActiveRunway) {
// pick by default // pick by default
applyOnFinal();
} else if (onRunway) { } else if (onRunway) {
if (m_airportLocation->type() == FGPositioned::AIRPORT) { if (m_airportLocation->type() == FGPositioned::AIRPORT) {
m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident())); m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
@ -1052,15 +791,7 @@ void LocationController::onCollectConfig()
m_config->setArg("nav1", QString("%1:%2").arg(runway->headingDeg()).arg(mhz)); m_config->setArg("nav1", QString("%1:%2").arg(runway->headingDeg()).arg(mhz));
} }
if (m_onFinal) { applyOnFinal();
m_config->setArg("glideslope", std::string("3.0"));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
m_config->setArg("on-ground", std::string("false"));
applyAirspeed();
applyAltitude();
}
} else if (m_airportLocation->type() == FGPositioned::HELIPORT) { } else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident())); m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
} }
@ -1228,5 +959,3 @@ void LocationController::addToRecent(FGPositionedRef pos)
QSettings settings; QSettings settings;
settings.setValue("recent-locations", savePositionList(m_recentLocations)); settings.setValue("recent-locations", savePositionList(m_recentLocations));
} }
#include "LocationController.moc"

View file

@ -31,7 +31,7 @@
#include "QmlPositioned.hxx" #include "QmlPositioned.hxx"
#include "UnitsModel.hxx" #include "UnitsModel.hxx"
class NavSearchModel; class NavaidSearchModel;
class LocationController : public QObject class LocationController : public QObject
{ {
@ -39,7 +39,7 @@ class LocationController : public QObject
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
Q_PROPERTY(NavSearchModel* searchModel MEMBER m_searchModel CONSTANT) Q_PROPERTY(NavaidSearchModel* searchModel MEMBER m_searchModel CONSTANT)
Q_PROPERTY(QList<QObject*> airportRunways READ airportRunways NOTIFY baseLocationChanged) Q_PROPERTY(QList<QObject*> airportRunways READ airportRunways NOTIFY baseLocationChanged)
Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged) Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged)
@ -194,8 +194,9 @@ private:
void applyPositionOffset(); void applyPositionOffset();
void applyAltitude(); void applyAltitude();
void applyAirspeed(); void applyAirspeed();
void applyOnFinal();
NavSearchModel* m_searchModel = nullptr; NavaidSearchModel* m_searchModel = nullptr;
FGPositionedRef m_location; FGPositionedRef m_location;
FGAirportRef m_airportLocation; // valid if m_location is an FGAirport FGAirportRef m_airportLocation; // valid if m_location is an FGAirport

View file

@ -0,0 +1,293 @@
// NavaidSearchModel.cxx - expose navaids via a QabstractListModel
//
// Written by James Turner, started July 2018.
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "NavaidSearchModel.hxx"
#include <QTimer>
#include "AirportDiagram.hxx"
#include <Navaids/navrecord.hxx>
#include "QmlPositioned.hxx"
using namespace flightgear;
QString fixNavaidName(QString s)
{
// split into words
QStringList words = s.split(QChar(' '));
QStringList changedWords;
Q_FOREACH(QString w, words) {
QString up = w.toUpper();
// expand common abbreviations
// note these are not translated, since they are abbreivations
// for English-langauge airports, mostly in the US/Canada
if (up == "FLD") {
changedWords.append("Field");
continue;
}
if (up == "CO") {
changedWords.append("County");
continue;
}
if ((up == "MUNI") || (up == "MUN")) {
changedWords.append("Municipal");
continue;
}
if (up == "MEM") {
changedWords.append("Memorial");
continue;
}
if (up == "RGNL") {
changedWords.append("Regional");
continue;
}
if (up == "CTR") {
changedWords.append("Center");
continue;
}
if (up == "INTL") {
changedWords.append("International");
continue;
}
// occurs in many Australian airport names in our DB
if (up == "(NSW)") {
changedWords.append("(New South Wales)");
continue;
}
if ((up == "VOR") || (up == "NDB")
|| (up == "VOR-DME") || (up == "VORTAC")
|| (up == "NDB-DME")
|| (up == "AFB") || (up == "RAF"))
{
changedWords.append(w);
continue;
}
if ((up =="[X]") || (up == "[H]") || (up == "[S]")) {
continue; // consume
}
QChar firstChar = w.at(0).toUpper();
w = w.mid(1).toLower();
w.prepend(firstChar);
changedWords.append(w);
}
return changedWords.join(QChar(' '));
}
class IdentSearchFilter : public FGPositioned::TypeFilter
{
public:
IdentSearchFilter(LauncherController::AircraftType aircraft, bool airportsOnly)
{
if (!airportsOnly) {
addType(FGPositioned::VOR);
addType(FGPositioned::FIX);
addType(FGPositioned::NDB);
}
if (aircraft == LauncherController::Helicopter) {
addType(FGPositioned::HELIPAD);
}
if (aircraft == LauncherController::Seaplane) {
addType(FGPositioned::SEAPORT);
} else {
addType(FGPositioned::AIRPORT);
}
}
};
void NavaidSearchModel::clear()
{
beginResetModel();
m_items.clear();
m_ids.clear();
m_searchActive = false;
m_search.reset();
endResetModel();
emit searchActiveChanged();
emit haveExistingSearchChanged();
}
void NavaidSearchModel::setSearch(QString t, NavaidSearchModel::AircraftType aircraft)
{
beginResetModel();
m_items.clear();
m_ids.clear();
std::string term(t.toUpper().toStdString());
IdentSearchFilter filter(static_cast<LauncherController::AircraftType>(aircraft), m_airportsOnly);
FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true);
// truncate based on max results
if ((m_maxResults > 0) && (exactMatches.size() > m_maxResults)) {
auto it = exactMatches.begin() + m_maxResults;
exactMatches.erase(it, exactMatches.end());
}
m_ids.reserve(exactMatches.size());
m_items.reserve(exactMatches.size());
for (auto match : exactMatches) {
m_ids.push_back(match->guid());
m_items.push_back(match);
}
endResetModel();
m_search.reset(new NavDataCache::ThreadedGUISearch(term, m_airportsOnly));
QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
m_searchActive = true;
emit searchActiveChanged();
emit haveExistingSearchChanged();
}
bool NavaidSearchModel::haveExistingSearch() const
{
return m_searchActive || (!m_items.empty());
}
int NavaidSearchModel::rowCount(const QModelIndex &) const
{
return m_ids.size();
}
QVariant NavaidSearchModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
FGPositionedRef pos = itemAtRow(index.row());
switch (role) {
case GuidRole: return static_cast<qlonglong>(pos->guid());
case IdentRole: return QString::fromStdString(pos->ident());
case NameRole:
return fixNavaidName(QString::fromStdString(pos->name()));
case NavFrequencyRole: {
FGNavRecord* nav = fgpositioned_cast<FGNavRecord>(pos);
return nav ? nav->get_freq() : 0;
}
case TypeRole: return static_cast<QmlPositioned::Type>(pos->type());
case IconRole:
return AirportDiagram::iconForPositioned(pos,
AirportDiagram::SmallIcons | AirportDiagram::LargeAirportPlans);
}
return {};
}
FGPositionedRef NavaidSearchModel::itemAtRow(unsigned int row) const
{
FGPositionedRef pos = m_items[row];
if (!pos.valid()) {
pos = NavDataCache::instance()->loadById(m_ids[row]);
m_items[row] = pos;
}
return pos;
}
void NavaidSearchModel::setItems(const FGPositionedList &items)
{
beginResetModel();
m_searchActive = false;
m_items = items;
m_ids.clear();
for (unsigned int i=0; i < items.size(); ++i) {
m_ids.push_back(m_items[i]->guid());
}
endResetModel();
emit searchActiveChanged();
}
QHash<int, QByteArray> NavaidSearchModel::roleNames() const
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[GeodRole] = "geod";
result[GuidRole] = "guid";
result[IdentRole] = "ident";
result[NameRole] = "name";
result[IconRole] = "icon";
result[TypeRole] = "type";
result[NavFrequencyRole] = "frequency";
return result;
}
qlonglong NavaidSearchModel::exactMatch() const
{
if (m_searchActive || (m_ids.size() != 1))
return 0; // no exact match
return m_ids.back(); // which is also the front
}
void NavaidSearchModel::onSearchResultsPoll()
{
if (m_search.isNull()) {
return;
}
PositionedIDVec newIds = m_search->results();
int newTotalSize = m_ids.size() + newIds.size();
if ((m_maxResults > 0) && (newTotalSize > m_maxResults)) {
// truncate new results as necessary
int numNewAllowed = m_maxResults - m_ids.size();
auto it = newIds.begin() + numNewAllowed;
newIds.erase(it, newIds.end());
// possible that newIDs is empty now
}
if (!newIds.empty()) {
beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
for (auto id : newIds) {
m_ids.push_back(id);
m_items.push_back({}); // null ref
}
endInsertRows();
}
if (m_search->isComplete()) {
m_searchActive = false;
m_search.reset();
emit searchComplete();
emit searchActiveChanged();
emit haveExistingSearchChanged();
} else {
QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
}
}

View file

@ -0,0 +1,108 @@
// NavaidSearchModel.hxx - expose navaids via a QabstractListModel
//
// Written by James Turner, started July 2018.
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef NAVAIDSEARCHMODEL_HXX
#define NAVAIDSEARCHMODEL_HXX
#include <QAbstractListModel>
#include "LauncherController.hxx"
#include <Navaids/positioned.hxx>
#include <Navaids/NavDataCache.hxx>
class NavaidSearchModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool isSearchActive READ isSearchActive NOTIFY searchActiveChanged)
Q_PROPERTY(bool haveExistingSearch READ haveExistingSearch NOTIFY haveExistingSearchChanged)
Q_PROPERTY(bool airportsOnly MEMBER m_airportsOnly NOTIFY airportsOnlyChanged)
Q_PROPERTY(int maxResults MEMBER m_maxResults NOTIFY maxResultsChanged)
Q_PROPERTY(qlonglong exactMatch READ exactMatch NOTIFY searchActiveChanged)
enum Roles {
GeodRole = Qt::UserRole + 1,
GuidRole = Qt::UserRole + 2,
IdentRole = Qt::UserRole + 3,
NameRole = Qt::UserRole + 4,
IconRole = Qt::UserRole + 5,
TypeRole = Qt::UserRole + 6,
NavFrequencyRole = Qt::UserRole + 7
};
public:
NavaidSearchModel() { }
enum AircraftType
{
Airplane = LauncherController::Airplane,
Seaplane = LauncherController::Seaplane,
Helicopter = LauncherController::Helicopter,
Airship = LauncherController::Airship
};
Q_ENUMS(AircraftType)
Q_INVOKABLE void setSearch(QString t, AircraftType aircraft);
Q_INVOKABLE void clear();
bool isSearchActive() const
{
return m_searchActive;
}
bool haveExistingSearch() const;
int rowCount(const QModelIndex&) const override;
QVariant data(const QModelIndex& index, int role) const override;
FGPositionedRef itemAtRow(unsigned int row) const;
void setItems(const FGPositionedList& items);
QHash<int, QByteArray> roleNames() const override;
qlonglong exactMatch() const;
Q_SIGNALS:
void searchComplete();
void searchActiveChanged();
void haveExistingSearchChanged();
void airportsOnlyChanged();
void maxResultsChanged();
private slots:
void onSearchResultsPoll();
private:
PositionedIDVec m_ids;
mutable FGPositionedList m_items;
bool m_searchActive = false;
bool m_airportsOnly = false;
int m_maxResults = 0;
QScopedPointer<flightgear::NavDataCache::ThreadedGUISearch> m_search;
};
#endif // NAVAIDSEARCHMODEL_HXX

View file

@ -1,7 +1,145 @@
import QtQuick 2.0 import QtQuick 2.4
import QtQuick.Window 2.0
import FlightGear 1.0
import FlightGear.Launcher 1.0
import "."
LineEdit LineEdit
{ {
id: root
placeholder: "KSFO" placeholder: "KSFO"
suggestedWidthString: "XXXX" suggestedWidthString: "XXXX"
Positioned {
id: airport
}
function selectAirport(guid)
{
airport.guid = guid
text = airport.ident
// we don't want this to trigger a search ....
searchTimer.stop();
}
onActiveFocusChanged: {
if (activeFocus) {
OverlayShared.globalOverlay.showOverlayAtItemOffset(overlay, root, Qt.point(xOffsetForEditFrame, root.height + Style.margin))
} else {
OverlayShared.globalOverlay.dismissOverlay()
searchCompleter.clear();
if (!airport.valid) {
text = ""; // ensure we always contain a valid ICAO or nothing
// we don't want this to trigger a search ....
searchTimer.stop();
}
}
}
NavaidSearch {
id: searchCompleter
airportsOnly: true
maxResults: 20
onSearchComplete: {
if (exactMatch !== 0) {
selectAirport(exactMatch)
return;
}
}
}
onTextChanged: {
searchTimer.restart();
}
Timer {
id: searchTimer
interval: 400
onTriggered: {
if (root.text.length >= 2) {
searchCompleter.setSearch(root.text, NavaidSearch.Airplane)
} else {
// ensure we update with no search (cancel, effectively)
searchCompleter.clear();
}
}
}
StyledText {
anchors.left: parent.right
anchors.leftMargin: Style.margin
anchors.verticalCenter: parent.verticalCenter
text: airport.name
visible: airport.valid
width: Style.strutSize * 3
elide: Text.ElideRight
}
Component {
id: overlay
Rectangle {
id: selectionPopup
color: "white"
height: choicesColumn.childrenRect.height + Style.margin * 2
width: choicesColumn.width + Style.margin * 2
visible: searchCompleter.haveExistingSearch
Rectangle {
border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
}
// choice layout column
Column {
id: choicesColumn
spacing: Style.margin
x: Style.margin
y: Style.margin
width: menuWidth
function calculateMenuWidth()
{
var minWidth = 0;
for (var i = 0; i < choicesRepeater.count; i++) {
minWidth = Math.max(minWidth, choicesRepeater.itemAt(i).implicitWidth);
}
return minWidth;
}
readonly property int menuWidth: calculateMenuWidth()
// main item repeater
Repeater {
id: choicesRepeater
model: searchCompleter
delegate:
Text {
id: choiceText
text: model.ident + " - " + model.name
height: implicitHeight + Style.margin
font.pixelSize: Style.baseFontPixelSize
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
MouseArea {
id: choiceArea
width: selectionPopup.width // full width of the popup
height: parent.height
hoverEnabled: true
onClicked: {
root.selectAirport(model.guid)
root.focus = false
}
}
} // of Text delegate
} // text repeater
} // text column
}
} // of overlay component
} }

View file

@ -1,5 +1,6 @@
import QtQuick 2.4 import QtQuick 2.4
import FlightGear.Launcher 1.0 import FlightGear.Launcher 1.0
import FlightGear 1.0
import "." import "."
Item { Item {
@ -128,20 +129,17 @@ Item {
width: parent.width width: parent.width
spacing: Style.margin spacing: Style.margin
IntegerSpinbox { NumericalEdit {
label: qsTr("Cruise speed:") label: qsTr("Cruise speed:")
suffix: "kts" unitsMode: Units.Speed
min: 0
max: 10000 // more for spaceships?
step: 5
maxDigits: 5
} }
// padding // padding
Item { width: Style.strutSize; height: 1 } Item { width: Style.strutSize; height: 1 }
LocationAltitudeRow { NumericalEdit {
label: qsTr("Cruise altitude:")
unitsMode: Units.AltitudeIncludingMeters
} }
} }
@ -170,7 +168,7 @@ Item {
TimeEdit { TimeEdit {
id: enrouteEstimate id: enrouteEstimate
label: qsTr("Estimate enroute time:") label: qsTr("Estimated enroute time:")
} }
Item { width: Style.strutSize; height: 1 } Item { width: Style.strutSize; height: 1 }

View file

@ -1,4 +1,5 @@
import QtQuick 2.4 import QtQuick 2.4
import FlightGear 1.0
import "." import "."
Item { Item {
@ -158,5 +159,15 @@ Item {
] ]
} }
Overlay {
id: popupOverlay
anchors.fill: parent
z: 200
Component.onCompleted: {
OverlayShared.globalOverlay = this
}
}
} }

View file

@ -16,6 +16,8 @@ FocusScope {
property bool useFullWidth: false property bool useFullWidth: false
readonly property int xOffsetForEditFrame: editFrame.x
implicitHeight: editFrame.height implicitHeight: editFrame.height
implicitWidth: suggestedWidth + label.implicitWidth + (Style.margin * 3) implicitWidth: suggestedWidth + label.implicitWidth + (Style.margin * 3)

31
src/GUI/qml/Overlay.qml Normal file
View file

@ -0,0 +1,31 @@
import QtQuick 2.4
Item {
function showOverlay(comp)
{
activeOverlayLoader.sourceComponent = comp;
activeOverlayLoader.visible = true;
}
function showOverlayAtItemOffset(comp, item, offset)
{
var pt = mapFromItem(item, offset.x, offset.y)
activeOverlayLoader.sourceComponent = comp;
activeOverlayLoader.visible = true;
activeOverlayLoader.x = pt.x;
activeOverlayLoader.y = pt.y;
}
function dismissOverlay()
{
activeOverlayLoader.sourceComponent = null
activeOverlayLoader.visible = false;
}
Loader
{
id: activeOverlayLoader
// no size, size to the component
}
}

View file

@ -0,0 +1,6 @@
pragma Singleton
import QtQuick 2.0
QtObject {
property var globalOverlay
}

View file

@ -115,6 +115,8 @@
<file>qml/TimeEdit.qml</file> <file>qml/TimeEdit.qml</file>
<file>qml/AirportEntry.qml</file> <file>qml/AirportEntry.qml</file>
<file>qml/NumericalEdit.qml</file> <file>qml/NumericalEdit.qml</file>
<file>qml/Overlay.qml</file>
<file>qml/OverlayShared.qml</file>
</qresource> </qresource>
<qresource prefix="/preview"> <qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file> <file alias="close-icon">preview-close.png</file>

View file

@ -2444,7 +2444,7 @@ public:
bool quit; bool quit;
}; };
NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term) : NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term, bool onlyAirports) :
d(new ThreadedGUISearchPrivate) d(new ThreadedGUISearchPrivate)
{ {
SGPath p = NavDataCache::instance()->path(); SGPath p = NavDataCache::instance()->path();
@ -2452,8 +2452,14 @@ NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term) :
std::string pathUtf8 = p.utf8Str(); std::string pathUtf8 = p.utf8Str();
sqlite3_open_v2(pathUtf8.c_str(), &d->db, openFlags, NULL); sqlite3_open_v2(pathUtf8.c_str(), &d->db, openFlags, NULL);
std::string sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term std::string sql;
+ "%' AND ((type >= 1 AND type <= 3) OR ((type >= 9 AND type <= 11))) "; if (onlyAirports) {
sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term
+ "%' AND (type >= 1 AND type <= 3)";
} else {
sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term
+ "%' AND ((type >= 1 AND type <= 3) OR ((type >= 9 AND type <= 11))) ";
}
sqlite3_prepare_v2(d->db, sql.c_str(), sql.length(), &d->query, NULL); sqlite3_prepare_v2(d->db, sql.c_str(), sql.length(), &d->query, NULL);
d->start(); d->start();

View file

@ -309,7 +309,7 @@ public:
class ThreadedGUISearch class ThreadedGUISearch
{ {
public: public:
ThreadedGUISearch(const std::string& term); ThreadedGUISearch(const std::string& term, bool onlyAirports);
~ThreadedGUISearch(); ~ThreadedGUISearch();
PositionedIDVec results() const; PositionedIDVec results() const;