diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index 2a4372c66..b99056674 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -154,6 +154,8 @@ if (HAVE_QT) QmlRadioButtonHelper.hxx UnitsModel.cxx UnitsModel.hxx + NavaidSearchModel.hxx + NavaidSearchModel.cxx ) set_property(TARGET fgqmlui PROPERTY AUTOMOC ON) diff --git a/src/GUI/LauncherController.cxx b/src/GUI/LauncherController.cxx index fdc38546e..c4f2e3ab5 100644 --- a/src/GUI/LauncherController.cxx +++ b/src/GUI/LauncherController.cxx @@ -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(); - qRegisterMetaTypeStreamOperators("Quantity"); - m_serversModel = new MPServersModel(this); m_location = new LocationController(this); m_locationHistory = new RecentLocationsModel(this); @@ -137,6 +135,8 @@ void LauncherController::initQML() qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LaunchConfig", "Singleton API"); qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API"); + qmlRegisterType("FlightGear", 1, 0, "NavaidSearch"); + qmlRegisterUncreatableType("FlightGear", 1, 0, "Units", "Only for enum"); qmlRegisterType("FlightGear", 1, 0, "UnitsModel"); @@ -158,6 +158,8 @@ void LauncherController::initQML() qmlRegisterType("FlightGear", 1, 0, "NavaidDiagram"); qmlRegisterType("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())); diff --git a/src/GUI/LocationController.cxx b/src/GUI/LocationController.cxx index 69d07b5cb..e4036e468 100644 --- a/src/GUI/LocationController.cxx +++ b/src/GUI/LocationController.cxx @@ -34,6 +34,7 @@ #include "NavaidDiagram.hxx" #include "LaunchConfig.hxx" #include "DefaultAircraftLocator.hxx" +#include "NavaidSearchModel.hxx" #include #include @@ -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(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(pos->guid()); - case IdentRole: return QString::fromStdString(pos->ident()); - case NameRole: - return fixNavaidName(QString::fromStdString(pos->name())); - - case NavFrequencyRole: { - FGNavRecord* nav = fgpositioned_cast(pos); - return nav ? nav->get_freq() : 0; - } - - case TypeRole: return static_cast(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 roleNames() const override - { - QHash 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 m_search; -}; - - LocationController::LocationController(QObject *parent) : QObject(parent) { - qmlRegisterUncreatableType("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" diff --git a/src/GUI/LocationController.hxx b/src/GUI/LocationController.hxx index 6f422bcb3..b54b88f84 100644 --- a/src/GUI/LocationController.hxx +++ b/src/GUI/LocationController.hxx @@ -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 airportRunways READ airportRunways NOTIFY baseLocationChanged) Q_PROPERTY(QList 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 diff --git a/src/GUI/NavaidSearchModel.cxx b/src/GUI/NavaidSearchModel.cxx new file mode 100644 index 000000000..0a996e5b6 --- /dev/null +++ b/src/GUI/NavaidSearchModel.cxx @@ -0,0 +1,293 @@ +// NavaidSearchModel.cxx - expose navaids via a QabstractListModel +// +// Written by James Turner, started July 2018. +// +// Copyright (C) 2018 James Turner +// +// 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 + +#include "AirportDiagram.hxx" +#include +#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(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(pos->guid()); + case IdentRole: return QString::fromStdString(pos->ident()); + case NameRole: + return fixNavaidName(QString::fromStdString(pos->name())); + + case NavFrequencyRole: { + FGNavRecord* nav = fgpositioned_cast(pos); + return nav ? nav->get_freq() : 0; + } + + case TypeRole: return static_cast(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 NavaidSearchModel::roleNames() const +{ + QHash 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())); + } +} diff --git a/src/GUI/NavaidSearchModel.hxx b/src/GUI/NavaidSearchModel.hxx new file mode 100644 index 000000000..84b849a9e --- /dev/null +++ b/src/GUI/NavaidSearchModel.hxx @@ -0,0 +1,108 @@ +// NavaidSearchModel.hxx - expose navaids via a QabstractListModel +// +// Written by James Turner, started July 2018. +// +// Copyright (C) 2018 James Turner +// +// 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 + +#include "LauncherController.hxx" + +#include +#include + +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 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 m_search; +}; + + +#endif // NAVAIDSEARCHMODEL_HXX diff --git a/src/GUI/qml/AirportEntry.qml b/src/GUI/qml/AirportEntry.qml index c41d95fb6..ee968ba60 100644 --- a/src/GUI/qml/AirportEntry.qml +++ b/src/GUI/qml/AirportEntry.qml @@ -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 } diff --git a/src/GUI/qml/FlightPlan.qml b/src/GUI/qml/FlightPlan.qml index 2dcabb375..4d2469a68 100644 --- a/src/GUI/qml/FlightPlan.qml +++ b/src/GUI/qml/FlightPlan.qml @@ -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 } diff --git a/src/GUI/qml/Launcher.qml b/src/GUI/qml/Launcher.qml index 3ae4c2a7d..0fae0003d 100644 --- a/src/GUI/qml/Launcher.qml +++ b/src/GUI/qml/Launcher.qml @@ -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 + } + } + } diff --git a/src/GUI/qml/LineEdit.qml b/src/GUI/qml/LineEdit.qml index 75fdadbb6..dfc58d20d 100644 --- a/src/GUI/qml/LineEdit.qml +++ b/src/GUI/qml/LineEdit.qml @@ -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) diff --git a/src/GUI/qml/Overlay.qml b/src/GUI/qml/Overlay.qml new file mode 100644 index 000000000..537c2246c --- /dev/null +++ b/src/GUI/qml/Overlay.qml @@ -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 + } +} diff --git a/src/GUI/qml/OverlayShared.qml b/src/GUI/qml/OverlayShared.qml new file mode 100644 index 000000000..afaa2d4bc --- /dev/null +++ b/src/GUI/qml/OverlayShared.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQuick 2.0 + +QtObject { + property var globalOverlay +} diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index f96ebbc03..129016285 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -115,6 +115,8 @@ qml/TimeEdit.qml qml/AirportEntry.qml qml/NumericalEdit.qml + qml/Overlay.qml + qml/OverlayShared.qml preview-close.png diff --git a/src/Navaids/NavDataCache.cxx b/src/Navaids/NavDataCache.cxx index ebf12a96e..34b5e503d 100644 --- a/src/Navaids/NavDataCache.cxx +++ b/src/Navaids/NavDataCache.cxx @@ -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(); diff --git a/src/Navaids/NavDataCache.hxx b/src/Navaids/NavDataCache.hxx index f2f5a9083..683cedb2d 100644 --- a/src/Navaids/NavDataCache.hxx +++ b/src/Navaids/NavDataCache.hxx @@ -309,7 +309,7 @@ public: class ThreadedGUISearch { public: - ThreadedGUISearch(const std::string& term); + ThreadedGUISearch(const std::string& term, bool onlyAirports); ~ThreadedGUISearch(); PositionedIDVec results() const;