Working on airport entry
This commit is contained in:
parent
9098219032
commit
86786496a5
15 changed files with 640 additions and 311 deletions
|
@ -154,6 +154,8 @@ if (HAVE_QT)
|
|||
QmlRadioButtonHelper.hxx
|
||||
UnitsModel.cxx
|
||||
UnitsModel.hxx
|
||||
NavaidSearchModel.hxx
|
||||
NavaidSearchModel.cxx
|
||||
)
|
||||
|
||||
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "NavaidDiagram.hxx"
|
||||
#include "QmlRadioButtonHelper.hxx"
|
||||
#include "UnitsModel.hxx"
|
||||
#include "NavaidSearchModel.hxx"
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
|
@ -55,9 +56,6 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
|
|||
QObject(parent),
|
||||
m_window(window)
|
||||
{
|
||||
qRegisterMetaType<QuantityValue>();
|
||||
qRegisterMetaTypeStreamOperators<QuantityValue>("Quantity");
|
||||
|
||||
m_serversModel = new MPServersModel(this);
|
||||
m_location = new LocationController(this);
|
||||
m_locationHistory = new RecentLocationsModel(this);
|
||||
|
@ -137,6 +135,8 @@ void LauncherController::initQML()
|
|||
qmlRegisterUncreatableType<LaunchConfig>("FlightGear.Launcher", 1, 0, "LaunchConfig", "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");
|
||||
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
|
||||
|
||||
|
@ -158,6 +158,8 @@ void LauncherController::initQML()
|
|||
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
|
||||
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
|
||||
|
||||
qmlRegisterSingletonType(QUrl("qrc:///qml/OverlayShared.qml"), "FlightGear", 1, 0, "OverlayShared");
|
||||
|
||||
QNetworkDiskCache* diskCache = new QNetworkDiskCache(this);
|
||||
SGPath cachePath = globals->get_fg_home() / "PreviewsCache";
|
||||
diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str()));
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "NavaidDiagram.hxx"
|
||||
#include "LaunchConfig.hxx"
|
||||
#include "DefaultAircraftLocator.hxx"
|
||||
#include "NavaidSearchModel.hxx"
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
|
@ -49,81 +50,6 @@ using namespace flightgear;
|
|||
|
||||
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)
|
||||
{
|
||||
QVariantList vl;
|
||||
|
@ -164,214 +90,10 @@ FGPositionedList loadPositionedList(QVariant v)
|
|||
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) :
|
||||
QObject(parent)
|
||||
{
|
||||
qmlRegisterUncreatableType<NavSearchModel>("FlightGear.Launcher", 1, 0, "NavSearchModel", "no");
|
||||
m_searchModel = new NavSearchModel;
|
||||
|
||||
m_searchModel = new NavaidSearchModel;
|
||||
m_detailQml = 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()
|
||||
{
|
||||
if (m_skipFromArgs) {
|
||||
|
@ -1041,6 +779,7 @@ void LocationController::onCollectConfig()
|
|||
|
||||
if (m_useActiveRunway) {
|
||||
// pick by default
|
||||
applyOnFinal();
|
||||
} else if (onRunway) {
|
||||
if (m_airportLocation->type() == FGPositioned::AIRPORT) {
|
||||
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));
|
||||
}
|
||||
|
||||
if (m_onFinal) {
|
||||
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();
|
||||
}
|
||||
applyOnFinal();
|
||||
} else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
|
||||
m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
|
||||
}
|
||||
|
@ -1228,5 +959,3 @@ void LocationController::addToRecent(FGPositionedRef pos)
|
|||
QSettings settings;
|
||||
settings.setValue("recent-locations", savePositionList(m_recentLocations));
|
||||
}
|
||||
|
||||
#include "LocationController.moc"
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include "QmlPositioned.hxx"
|
||||
#include "UnitsModel.hxx"
|
||||
|
||||
class NavSearchModel;
|
||||
class NavaidSearchModel;
|
||||
|
||||
class LocationController : public QObject
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ class LocationController : public QObject
|
|||
|
||||
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*> airportParkings READ airportParkings NOTIFY baseLocationChanged)
|
||||
|
@ -194,8 +194,9 @@ private:
|
|||
void applyPositionOffset();
|
||||
void applyAltitude();
|
||||
void applyAirspeed();
|
||||
void applyOnFinal();
|
||||
|
||||
NavSearchModel* m_searchModel = nullptr;
|
||||
NavaidSearchModel* m_searchModel = nullptr;
|
||||
|
||||
FGPositionedRef m_location;
|
||||
FGAirportRef m_airportLocation; // valid if m_location is an FGAirport
|
||||
|
|
293
src/GUI/NavaidSearchModel.cxx
Normal file
293
src/GUI/NavaidSearchModel.cxx
Normal 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()));
|
||||
}
|
||||
}
|
108
src/GUI/NavaidSearchModel.hxx
Normal file
108
src/GUI/NavaidSearchModel.hxx
Normal 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
|
|
@ -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
|
||||
{
|
||||
id: root
|
||||
placeholder: "KSFO"
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
|
@ -128,20 +129,17 @@ Item {
|
|||
width: parent.width
|
||||
spacing: Style.margin
|
||||
|
||||
IntegerSpinbox {
|
||||
NumericalEdit {
|
||||
label: qsTr("Cruise speed:")
|
||||
suffix: "kts"
|
||||
min: 0
|
||||
max: 10000 // more for spaceships?
|
||||
step: 5
|
||||
maxDigits: 5
|
||||
unitsMode: Units.Speed
|
||||
}
|
||||
|
||||
// padding
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
||||
LocationAltitudeRow {
|
||||
|
||||
NumericalEdit {
|
||||
label: qsTr("Cruise altitude:")
|
||||
unitsMode: Units.AltitudeIncludingMeters
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +168,7 @@ Item {
|
|||
|
||||
TimeEdit {
|
||||
id: enrouteEstimate
|
||||
label: qsTr("Estimate enroute time:")
|
||||
label: qsTr("Estimated enroute time:")
|
||||
}
|
||||
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
|
@ -158,5 +159,15 @@ Item {
|
|||
]
|
||||
}
|
||||
|
||||
Overlay {
|
||||
id: popupOverlay
|
||||
anchors.fill: parent
|
||||
z: 200
|
||||
|
||||
Component.onCompleted: {
|
||||
OverlayShared.globalOverlay = this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ FocusScope {
|
|||
|
||||
property bool useFullWidth: false
|
||||
|
||||
readonly property int xOffsetForEditFrame: editFrame.x
|
||||
|
||||
implicitHeight: editFrame.height
|
||||
implicitWidth: suggestedWidth + label.implicitWidth + (Style.margin * 3)
|
||||
|
||||
|
|
31
src/GUI/qml/Overlay.qml
Normal file
31
src/GUI/qml/Overlay.qml
Normal 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
|
||||
}
|
||||
}
|
6
src/GUI/qml/OverlayShared.qml
Normal file
6
src/GUI/qml/OverlayShared.qml
Normal file
|
@ -0,0 +1,6 @@
|
|||
pragma Singleton
|
||||
import QtQuick 2.0
|
||||
|
||||
QtObject {
|
||||
property var globalOverlay
|
||||
}
|
|
@ -115,6 +115,8 @@
|
|||
<file>qml/TimeEdit.qml</file>
|
||||
<file>qml/AirportEntry.qml</file>
|
||||
<file>qml/NumericalEdit.qml</file>
|
||||
<file>qml/Overlay.qml</file>
|
||||
<file>qml/OverlayShared.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/preview">
|
||||
<file alias="close-icon">preview-close.png</file>
|
||||
|
|
|
@ -2444,7 +2444,7 @@ public:
|
|||
bool quit;
|
||||
};
|
||||
|
||||
NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term) :
|
||||
NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term, bool onlyAirports) :
|
||||
d(new ThreadedGUISearchPrivate)
|
||||
{
|
||||
SGPath p = NavDataCache::instance()->path();
|
||||
|
@ -2452,8 +2452,14 @@ NavDataCache::ThreadedGUISearch::ThreadedGUISearch(const std::string& term) :
|
|||
std::string pathUtf8 = p.utf8Str();
|
||||
sqlite3_open_v2(pathUtf8.c_str(), &d->db, openFlags, NULL);
|
||||
|
||||
std::string sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term
|
||||
+ "%' AND ((type >= 1 AND type <= 3) OR ((type >= 9 AND type <= 11))) ";
|
||||
std::string sql;
|
||||
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);
|
||||
|
||||
d->start();
|
||||
|
|
|
@ -309,7 +309,7 @@ public:
|
|||
class ThreadedGUISearch
|
||||
{
|
||||
public:
|
||||
ThreadedGUISearch(const std::string& term);
|
||||
ThreadedGUISearch(const std::string& term, bool onlyAirports);
|
||||
~ThreadedGUISearch();
|
||||
|
||||
PositionedIDVec results() const;
|
||||
|
|
Loading…
Reference in a new issue