2018-07-18 10:32:47 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2018-07-25 08:27:14 +00:00
|
|
|
addType(FGPositioned::AIRPORT);
|
|
|
|
|
|
|
|
switch (aircraft) {
|
|
|
|
case LauncherController::Airplane:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LauncherController::Helicopter:
|
|
|
|
addType(FGPositioned::HELIPORT);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LauncherController::Seaplane:
|
|
|
|
addType(FGPositioned::SEAPORT);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
addType(FGPositioned::HELIPORT);
|
2018-07-18 10:32:47 +00:00
|
|
|
addType(FGPositioned::SEAPORT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void NavaidSearchModel::clear()
|
|
|
|
{
|
|
|
|
beginResetModel();
|
|
|
|
m_items.clear();
|
|
|
|
m_ids.clear();
|
|
|
|
m_searchActive = false;
|
|
|
|
m_search.reset();
|
|
|
|
endResetModel();
|
|
|
|
emit searchActiveChanged();
|
|
|
|
emit haveExistingSearchChanged();
|
|
|
|
}
|
|
|
|
|
2018-08-11 20:02:47 +00:00
|
|
|
qlonglong NavaidSearchModel::guidAtIndex(int index) const
|
|
|
|
{
|
|
|
|
if ((index < 0) || (index >= m_ids.size()))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return m_ids.at(index);
|
|
|
|
}
|
|
|
|
|
2018-07-18 10:32:47 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2018-08-11 20:02:47 +00:00
|
|
|
|
|
|
|
resort();
|
2018-07-18 10:32:47 +00:00
|
|
|
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
|
|
|
|
{
|
2018-08-11 20:02:47 +00:00
|
|
|
if (m_maxResults > 0)
|
|
|
|
return std::min(static_cast<int>(m_ids.size()), m_maxResults);
|
|
|
|
|
|
|
|
return static_cast<int>(m_ids.size());
|
2018-07-18 10:32:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2018-08-11 20:02:47 +00:00
|
|
|
// don't sort in this case
|
2018-07-18 10:32:47 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-08-11 20:02:47 +00:00
|
|
|
int NavaidSearchModel::numResults() const
|
|
|
|
{
|
|
|
|
return static_cast<int>(m_ids.size());
|
|
|
|
}
|
|
|
|
|
2018-07-18 10:32:47 +00:00
|
|
|
void NavaidSearchModel::onSearchResultsPoll()
|
|
|
|
{
|
|
|
|
if (m_search.isNull()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
PositionedIDVec newIds = m_search->results();
|
|
|
|
if (!newIds.empty()) {
|
2018-08-11 20:02:47 +00:00
|
|
|
beginResetModel(); // reset the model since we will re-sort
|
2018-07-18 10:32:47 +00:00
|
|
|
for (auto id : newIds) {
|
|
|
|
m_ids.push_back(id);
|
|
|
|
m_items.push_back({}); // null ref
|
|
|
|
}
|
2018-08-11 20:02:47 +00:00
|
|
|
resort();
|
|
|
|
endResetModel();
|
2018-07-18 10:32:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_search->isComplete()) {
|
|
|
|
m_searchActive = false;
|
|
|
|
m_search.reset();
|
|
|
|
emit searchComplete();
|
|
|
|
emit searchActiveChanged();
|
|
|
|
emit haveExistingSearchChanged();
|
|
|
|
} else {
|
|
|
|
QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
|
|
|
|
}
|
|
|
|
}
|
2018-08-11 20:02:47 +00:00
|
|
|
|
|
|
|
void NavaidSearchModel::resort()
|
|
|
|
{
|
|
|
|
if (!m_airportsOnly) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear m_items
|
|
|
|
std::fill(m_items.begin(), m_items.end(), FGPositionedRef{});
|
|
|
|
|
|
|
|
// build runway length cache
|
|
|
|
std::map<PositionedID, double> longestRunwayCache;
|
|
|
|
for (auto a : m_ids) {
|
|
|
|
const FGAirportRef apt = FGPositioned::loadById<FGAirport>(a);
|
|
|
|
if (apt) {
|
|
|
|
const auto rwy = apt->longestRunway();
|
|
|
|
if (rwy) {
|
|
|
|
longestRunwayCache[a] = rwy->lengthFt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::sort(m_ids.begin(), m_ids.end(),
|
|
|
|
[&longestRunwayCache](const PositionedID a, const PositionedID& b)
|
|
|
|
{
|
|
|
|
return longestRunwayCache[a] > longestRunwayCache[b];
|
|
|
|
});
|
|
|
|
}
|